I need some sort of visual feedback of mouse position when it is clicked or clicked and dragged within a widget or a rectangular area. The visual feed back should stay where the mouse is released. I did something like the following, but it is not exactly what I'm tying to do:
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
class test(QLabel):
def __init__(self):
super(test, self).__init__()
self.setMouseTracking(True)
self.resize(300, 300)
self.handle = QPushButton()
self.handle.setFixedSize(15, 15)
self.handle.setParent(self)
self.handle.setText("+")
self.handle.setStyleSheet("background: none;"
"border: 1px solid;"
"border-radius: 7px;")
def mousePressEvent(self, pos):
if pos.button() == Qt.LeftButton:
self.handle.move(pos.x(), pos.y())
print(str(pos.x()) + ", " + str(pos.y()))
def mouseMoveEvent(self, pos):
if pos.buttons() & Qt.LeftButton:
self.handle.move(pos.x(), pos.y())
print(str(pos.x()) + ", " + str(pos.y()))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
t = test()
t.show()
sys.exit(app.exec_())
The problems with the above code are:
The button's position is calculated from its top left corner, so I have to do something like
self.handle.move(pos.x() -7, pos.y() -7)
to make it appear in the center, which is very inconsistent.
The visual feedback should stay within the widget or area and not go out of bounds when the mouse does. Again, I could work around that with a few lines of inconsistent code (as I'm not an expert).
I was looking for something that can help me with achieving it and the best best thing I come across was to install pyqtgraph. But I doubt if it will take more resources and adding a new library is going to complicate things for me. Or is this my best bet?
The image below shows something similar.
As indicated in the post, it is enough to take into account the geometry of the dragged element and the window:
import sys
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QApplication, QLabel
class TestLabel(QLabel):
def __init__(self):
super(TestLabel, self).__init__()
self.setMouseTracking(True)
self.resize(300, 300)
self.handle = QLabel(self)
self.handle.setFixedSize(15, 15)
self.handle.setText("+")
self.handle.setStyleSheet("border: 1px solid;" "border-radius: 7px;")
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton and self.rect().contains(event.pos()):
self.handle.move(event.pos() - self.handle.rect().center())
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton and self.rect().contains(event.pos()):
self.handle.move(event.pos() - self.handle.rect().center())
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
t = TestLabel()
t.show()
sys.exit(app.exec_())
Finding the name of the place where I drag the mouse in pyqt
position where mouse is released
def dropEvent(self, event): # Drag and Drop in Camera View
mimeData = QtCore.QMimeData()
format = 'application/x-qabstractitemmodeldatalist'
name_str = codecs.decode(data,'utf-8')
if mimeData.hasText:
destination = self.childAt(event.pos()) # position where mouse is released
destination.objectName() # object name where mouse is released
Related
I have a widget containing two buttons that can be (drag and drop) swapped using the mouse middle button. I am trying to restrain the mouse cursor from leaving the QWidget area when dragging and dropping a Qpushbutton... I am using dragMoveEvent() which offsets the cursor every time it crosses the border of the widget. It works when you move the mouse slowly but fast movements will make the cursor leave the area. What is the best way to make this happen? Thanks.
PS: Go to the Drag and Drop area for reference
import os
import random
import sys
import time
from PySide2 import QtOpenGL
from PySide2 import QtWidgets
from PySide2.QtCore import QEvent, QMimeData, QPoint, QRect
from PySide2.QtGui import QCursor, QDrag, QWindow
# import nuke
# import nukescripts
from collapse import Collapse
try:
from PySide import QtGui, QtCore
except ImportError:
from PySide2 import QtCore
from PySide2 import QtWidgets as QtGui
from PySide2 import QtGui as QtG
class CreateNodeBoard(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.nukePathSeparator = "/"
#self.toolPath = self.getFullPathWithExt()
self.currentDir = os.path.dirname(os.path.realpath(__file__))
################################################################################
# GUI
################################################################################
self.setMinimumWidth(350)
self.mainLayout = QtGui.QVBoxLayout()
self.mainLayout.setSpacing(0)
self.mainLayout.setAlignment(QtCore.Qt.AlignTop)
self.setLayout(self.mainLayout)
self.target = None
self.setAcceptDrops(True)
self.nodeBoardWidget = QtGui.QWidget()
self.nodeBoardWidget.setAcceptDrops(True)
nodeBoardVLayout = QtWidgets.QVBoxLayout()
self.nodeBoardWidget.setLayout(nodeBoardVLayout)
self.userButtonLayout = QtGui.QGridLayout()
nodeBoardVLayout.addLayout(self.userButtonLayout)
button1 = QtWidgets.QPushButton("a")
button2 = QtWidgets.QPushButton("b")
self.userButtonLayout.addWidget(button1)
self.userButtonLayout.addWidget(button2)
self.userButtonLayout.setAlignment(QtCore.Qt.AlignLeft)
self.mainLayout.addWidget(self.nodeBoardWidget)
def get_index(self, pos):
for i in range(self.userButtonLayout.count()):
buttonGlob = self.userButtonLayout.itemAt(i).widget().mapToGlobal(QPoint(0,0))
if QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(pos) and i != self.target:
return i
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
self.target = self.get_index(QCursor.pos())
else:
self.target = None
def mouseMoveEvent(self, event):
if event.buttons() & QtCore.Qt.MiddleButton and self.target is not None:
print("moving")
drag = QDrag(self.userButtonLayout.itemAt(self.target).widget())
pix = self.userButtonLayout.itemAt(self.target).widget().grab()
mimedata = QMimeData()
mimedata.setImageData(pix)
drag.setMimeData(mimedata)
drag.setPixmap(pix)
drag.setHotSpot(QPoint(40,10))
drag.exec_()
def dragMoveEvent(self, event):
cursorPos = QCursor.pos()
widgetPos = self.nodeBoardWidget.mapToGlobal(QPoint(0,0))
if cursorPos.x() < widgetPos.x() or cursorPos.y() < widgetPos.y():
QCursor.setPos(QCursor.pos().x() + 1 , QCursor.pos().y() + 1 )
event.accept()
def dragEnterEvent(self, event):
print("drag enter event")
if event.mimeData().hasImage():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
print("drop")
buttonGlob = self.userButtonLayout.itemAt(self.target).widget().mapToGlobal(self.pos())
if not QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(QCursor.pos()):
source = self.get_index(QCursor.pos())
if source is None:
return
i, j = max(self.target, source), min(self.target, source)
p1, p2 = self.userButtonLayout.getItemPosition(i), self.userButtonLayout.getItemPosition(j)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(i), *p2)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(j), *p1)
self.target = None
app = QtWidgets.QApplication(sys.argv)
# Create a Qt widget, which will be our window.
window = CreateNodeBoard()
window.show() # IMPORTANT!!!!! Windows are hidden by default.
# Start the event loop.
app.exec_()
EDIT
So after further investigation and testing the code on both LINUX/WINDOWS I have come to the conclusion that both behaviours are caused by the programme exceeding the maximum recursion limit. Any time the mouse cursor during the drag event leaves the assigned widget something causes the events to call each other and this causes my app to crash. Having this as a standalone app does not cause any problem and I do not know why? Also, I have no ideas how this programme goes into recursion.
The previous solution of mine where I tried to create a "safe zone" for the mouse did not solve the issue as far as there are certain mouse movements causing the same bug.
Here is a better version of a working code. As I have already mentioned it works as a standalone GUI but causes the programme to crash within another software environment.
from __future__ import print_function
import sys
try:
from PySide import QtWidgets, QtCore
except ImportError:
from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtOpenGL
class CreateNodeBoard(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
################################################################################
# GUI
################################################################################
self.setMinimumWidth(350)
self.mainLayout = QtWidgets.QVBoxLayout()
self.mainLayout.setSpacing(0)
self.mainLayout.setAlignment(QtCore.Qt.AlignTop)
self.setLayout(self.mainLayout)
self.target = None
self.targetWidget = None
self.setAcceptDrops(True)
################################################################################
# GUI - NODE BOARD
################################################################################
# Create a Layout to hold all widgets
self.nodeBoardWidget = QtWidgets.QWidget()
self.nodeBoardWidget.setAcceptDrops(True)
nodeBoardVLayout = QtWidgets.QVBoxLayout()
self.nodeBoardWidget.setLayout(nodeBoardVLayout)
# create a grid layout inside nodeBoaardVLayout and load buttons from JSON
self.userButtonLayout = QtWidgets.QGridLayout()
nodeBoardVLayout.addLayout(self.userButtonLayout)
button1 = QtWidgets.QPushButton('button1')
self.userButtonLayout.addWidget(button1)
button2 = QtWidgets.QPushButton('button2')
self.userButtonLayout.addWidget(button2)
button3 = QtWidgets.QPushButton('test button')
button3.clicked.connect(self._test)
self.userButtonLayout.addWidget(button3)
self.userButtonLayout.setAlignment(QtCore.Qt.AlignLeft)
self.mainLayout.addWidget(self.nodeBoardWidget)
nodeBoardVLayout.addStretch(1)
############################################################################
# test
############################################################################
def _test(self):
print(self.topLevelWidget())
def dragLeaveEvent(self, event):
print("dragLeaveEvent :", event)
# XXX: does not work on macOS
# self.drag.cancel()
# parent = self.parent().mapToGlobal(self.drag.hotSpot())
# QtGui.QCursor.setPos(parent.x() + 50, parent.y() + 50)
# XXX: could still causes a crash
# q = QMessageBox()
# q.setText('no can do')
# q.exec_()
def leaveEvent(self, event):
pass
def enterEvent(self, event):
pass
################################################################################
# DRAG AND DROP
################################################################################
def get_index(self, pos):
for i in range(self.userButtonLayout.count()):
buttonGlob = self.userButtonLayout.itemAt(
i).widget().mapToGlobal(QtCore.QPoint(0, 0))
if QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(pos) and i != self.target:
return i
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
self.target = self.get_index(QtGui.QCursor.pos())
else:
self.target = None
def mouseMoveEvent(self, event):
if event.buttons() and QtCore.Qt.MiddleButton and self.target is not None:
print("mouseClickEvent :", event)
self.drag = QtGui.QDrag(
self.userButtonLayout.itemAt(self.target).widget())
pix = self.userButtonLayout.itemAt(self.target).widget().grab()
mimedata = QtCore.QMimeData()
mimedata.setImageData(pix)
self.drag.setMimeData(mimedata)
self.drag.setPixmap(pix)
self.drag.setHotSpot(QtCore.QPoint(40, 10))
self.drag.exec_()
def dragMoveEvent(self, event):
# print("dragMoveEvent :", event)
cursorPos = QtGui.QCursor.pos()
widgetPos = self.nodeBoardWidget.mapToGlobal(QtCore.QPoint(0, 0))
if cursorPos.x() <= widgetPos.x() or cursorPos.y() <= widgetPos.y():
QtGui.QCursor.setPos(QtGui.QCursor.pos().x() +
10, QtGui.QCursor.pos().y() + 10)
def dragEnterEvent(self, event):
print("dragEnterEvent :", event)
# XXX: if ignored, will not crash but will not propagate events
event.accept()
def dropEvent(self, event):
# print("dropEvent :", event)
buttonGlob = self.userButtonLayout.itemAt(
self.target).widget().mapToGlobal(self.pos())
if not QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(QtGui.QCursor.pos()):
source = self.get_index(QtGui.QCursor.pos())
if source is None:
return
i, j = max(self.target, source), min(self.target, source)
p1, p2 = self.userButtonLayout.getItemPosition(
i), self.userButtonLayout.getItemPosition(j)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(i), *p2)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(j), *p1)
self.target = None
class TestWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.test_widget = QtWidgets.QWidget()
self.set_test()
_layout = QtWidgets.QHBoxLayout()
_layout.addWidget(CreateNodeBoard())
_layout.addWidget(self.test_widget)
self.setLayout(_layout)
def set_test(self):
"""Adjacent test widget"""
self.test_widget.setAutoFillBackground(True)
self.test_widget.setPalette(QtGui.QColor(255, 0, 0))
_test_layout = QtWidgets.QVBoxLayout()
_test_layout.addWidget(QtWidgets.QLabel('TEST WIDGET'))
self.test_widget.setLayout(_test_layout)
try:
import nukescripts
except ImportError as error:
APP = QtWidgets.QApplication(sys.argv)
WINDOW = TestWidget()
WINDOW.show()
APP.exec_()
else:
nukescripts.panels.registerWidgetAsPanel(
'TestWidget', 'DragDrop',
'DragDrop.MainWindow')
Premise
This answer is very limited to the specific question (prevent the user to move the mouse outside the boundaries of the given widget). Unfortunately, it's not a complete solution due to many conceptual problems in the given code:
both drag and drop events should always be managed by the widget that will actually handle them (in this case, nodeBoardWidget), not their parent;
getting the layout index of an item should always consider the item geometry (using a fixed size is discouraged, since widget sizes depend on lots of aspects) and the fact that an item could not be a widget (nested layouts are still layout items, so layout.itemAt().widget() could return None);
"swapping" items based on item indexes doesn't always keep the item index, as the resulting indexes could be unreliable (especially for grid layouts);
Partial solution
The important aspect to keep in mind is that trying to move the mouse by a small and fixed amount to "fix" its position is wrong, because mouse events are not continuous: if the mouse is moved very fast from x=0 to x=100 you don't get all values between 0 and 100, but only a small fraction of the intermediate positions.
For the same reason, trying to "fix" the position just by a fixed amount of pixels is wrong, since the offset can be variable depending on the mouse speed.
The above results in dragMoveEvent not being called if the mouse moves too fast outside the parent boundaries. While in your specific case it "works", it's only because you implemented the function in the parent (which, as said, is not the suggested approach, and this is a clear example of that reason). If the mouse position has to be "contained", the dragLeaveEvent must be implemented instead.
class CreateNodeBoard(QtWidgets.QWidget):
def __init__(self, parent = None):
# ...
self.targetWidget = None
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
widget = QtWidgets.QApplication.widgetAt(event.globalPos())
if (widget != self.nodeBoardWidget and
self.nodeBoardWidget.isAncestorOf(widget)):
self.targetWidget = widget
def mouseMoveEvent(self, event):
if self.targetWidget:
drag = QDrag(self.targetWidget)
pix = self.targetWidget.grab()
mimedata = QMimeData()
mimedata.setImageData(pix)
drag.setMimeData(mimedata)
drag.setPixmap(pix)
drag.setHotSpot(QPoint(40,10))
drag.exec_()
def dragEnterEvent(self, event):
if self.nodeBoardWidget.isAncestorOf(event.source()):
event.accept()
def dragLeaveEvent(self, event):
geo = self.nodeBoardWidget.rect().translated(
self.nodeBoardWidget.mapToGlobal(QtCore.QPoint()))
pos = QtGui.QCursor.pos()
if pos not in geo:
if pos.x() < geo.x():
pos.setX(geo.x())
elif pos.x() > geo.right():
pos.setX(geo.right())
if pos.y() < geo.y():
pos.setY(geo.y())
elif pos.y() > geo.bottom():
pos.setY(geo.bottom())
QtGui.QCursor.setPos(pos)
I strongly suggest you to study the above example and considerations, as your code has lots of conceptual issues that my answer could not fix if not by creating a completely new example done from scratch. Also, since it's pretty clear that you're getting your code from various sources found on the web, I also suggest you to do that with awareness. Imitation is a good way of learning, but not without understanding what is being done. Do your research on all functions and classes used, and study all the related documentation, starting from layout managers, drag and drop and not forgetting about official code styling practices.
FOUND A FIX:
the dragEnterEvent caused the whole thing to go into recursion which causes the app to crash. (Linux terminal kept showing maximum recursion limit exceeded every time I moved the dragEvent outside the widgets area)
So to fix this I have created a condition inside the dragEnterEvent that if the mouse cursor moves outside the widgets it should ignore the event.
################################################################################
# DRAG AND DROP
################################################################################
def get_index(self, pos):
for i in range(self.userButtonLayout.count()):
buttonGlob = self.userButtonLayout.itemAt(i).widget().mapToGlobal(QtCore.QPoint(0,0))
if QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(pos) and i != self.target:
return i
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
self.target = self.get_index(QtGui.QCursor.pos())
if event.buttons() & QtCore.Qt.MiddleButton and self.target is not None:
drag = QtGui.QDrag(self.userButtonLayout.itemAt(self.target).widget())
pix = self.userButtonLayout.itemAt(self.target).widget().grab()
mimedata = QtCore.QMimeData()
mimedata.setImageData(pix)
drag.setMimeData(mimedata)
drag.setPixmap(pix)
drag.setHotSpot(QtCore.QPoint(40,10))
drag.exec_()
else:
self.target = None
def dragLeaveEvent(self, event):
if self.cursorInWidget():
drag = QtGui.QDrag(self.userButtonLayout.itemAt(self.target).widget())
drag.cancel()
def cursorInWidget(self):
cursorPos = QtGui.QCursor.pos()
widgetWidth = self.nodeBoardWidget.geometry().width()
widgetHeight = self.nodeBoardWidget.geometry().height()
widgetPos = self.nodeBoardWidget.mapToGlobal(QtCore.QPoint(0,0))
if cursorPos.x() <= widgetPos.x() or cursorPos.y() <= widgetPos.y() or cursorPos.x() >= (widgetPos.x() + widgetWidth) or cursorPos.y() >= (widgetPos.y() + widgetHeight):
return False
else:
return True
def dragEnterEvent(self, event):
if self.cursorInWidget():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
buttonGlob = self.userButtonLayout.itemAt(self.target).widget().mapToGlobal(self.pos())
if not QtCore.QRect(buttonGlob.x(), buttonGlob.y(), 80, 23).contains(QtGui.QCursor.pos()):
source = self.get_index(QtGui.QCursor.pos())
if source is None:
return
i, j = max(self.target, source), min(self.target, source)
p1, p2 = self.userButtonLayout.getItemPosition(i), self.userButtonLayout.getItemPosition(j)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(i), *p2)
self.userButtonLayout.addItem(self.userButtonLayout.takeAt(j), *p1)
self.target = None
I have created a square at random position in canvas, but I don't know how do I move it to somewhere else in canvas by dragging it to desired position, please suggest some edits or a new method to achieve the proposed task, I am learning while doing so.
P.S. Attached a screenshot of the output window.
import sys
from random import randint
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow,QPushButton,QWidget
from PyQt5 import QtGui
from PyQt5.QtCore import QRect,Qt
from PyQt5.QtGui import QPainter,QBrush, QPen
from PyQt5 import QtCore
class Window(QMainWindow):
def __init__(self):
super(Window,self).__init__()
title="TeeSquare"
left=500
top=200
width=500
height=400
iconName="square.jpg"
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(iconName))
self.setGeometry(left, top, width, height)
self.should_paint_Rect = False
self.windowcomponents()
self.initUI()
self.show()
def initUI(self):
if self.should_paint_Rect:
self.label=QtWidgets.QLabel(self)
self.label.setText("circle")
def windowcomponents(self):
button=QPushButton("Add", self)
button.setGeometry(QRect(0, 0, 50, 28))
button.setIcon(QtGui.QIcon("Add.png"))
button.setToolTip("Create Square")
button.clicked.connect(self.paintRect)
def paintEvent(self, event):
super().paintEvent(event)
if self.should_paint_Rect:
painter = QtGui.QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.drawRect(randint(0,500), randint(0,500), 100, 100)
self.initUI()
self.label.move(60,100)
def paintRect(self, painter):
self.should_paint_Rect = True
self.update()
app = QApplication(sys.argv)
Rect=Window()
Rect.show()
sys.exit(app.exec_())
The logic of creating a dynamic element is to indicate a set of specific characteristics that by modifying these, the element is modified.
In this case you could use the center of the square, the dimensions of the square, etc. and that data must be implemented through a data structure that can be created from scratch for example by creating a class that has the information of the rectangle, but in Qt it is not necessary to create that element since it already exists and is QRect.
Now that that element has been identified, you can create a QRect whose top-left is random when the button is pressed, and use that QRect to paint it.
For dragging the procedure is:
Get the mouse click position.
Verify that the click is inside the rectangle.
Calculate the position relative to the rectangle.
When moving the mouse, the position of the rectangle must be updated based on the position of the mouse press.
Considering all of the above, the solution is:
import random
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.rect = QtCore.QRect()
self.drag_position = QtCore.QPoint()
button = QtWidgets.QPushButton("Add", self)
button.clicked.connect(self.on_clicked)
self.resize(640, 480)
#QtCore.pyqtSlot()
def on_clicked(self):
if self.rect.isNull():
self.rect = QtCore.QRect(
QtCore.QPoint(*random.sample(range(200), 2)), QtCore.QSize(100, 100)
)
self.update()
def paintEvent(self, event):
super().paintEvent(event)
if not self.rect.isNull():
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
painter.drawRect(self.rect)
def mousePressEvent(self, event):
if self.rect.contains(event.pos()):
self.drag_position = event.pos() - self.rect.topLeft()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if not self.drag_position.isNull():
self.rect.moveTopLeft(event.pos() - self.drag_position)
self.update()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.drag_position = QtCore.QPoint()
super().mouseReleaseEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Rect = Window()
Rect.show()
sys.exit(app.exec_())
I need to ask you guys for advice, as I'm running out of ideas. I'm working onclickable label. I have already done some "clickable label" class and handled mouseover event - which changes the label border and returns to the normal state when the mouse leaves.
Now I want it to have a custom glow effect on the label, but I want it to return to normal state after, let's say 0.5 s from a click.
I want my label with an image to imitate button. time.sleep does not work nice, especially with spamming clicks it freezes application main thread.
Hope I am not reinventing the wheel but as far as I know that's the way to go.
Heres the sample code, any answer is appreciated.
from PySide2.QtWidgets import QLabel, QSizePolicy, QGraphicsDropShadowEffect
from PySide2.QtGui import QPixmap
from PySide2.QtCore import (Signal, QEvent, QObject, QRect)
class ClickableLabel(QLabel):
def __init__(self, pic_path, width, height, border_color, click_function):
super(ClickableLabel, self).__init__()
# Setting the picture path and setting pixmap to label
self.pic_path = pic_path
self.pixmap = QPixmap(self.pic_path)
self.setPixmap(self.pixmap)
# Set the size
self.setFixedSize(width, height)
# Enable tracking and assign function
self.setMouseTracking(True)
self.click_function = click_function
# Set default
if border_color is None:
self.border_color = 'lightblue'
else:
self.border_color = border_color
def mouseMoveEvent(self, event):
# event.pos().x(), event.pos().y()
self.setStyleSheet("border: 1px solid " + str(self.border_color) + ";")
def leaveEvent(self, event):
# event.pos().x(), event.pos().y()
self.setStyleSheet("border: None")
def mousePressEvent(self, event):
self.click_function()
effect = QGraphicsDropShadowEffect(self)
effect.setOffset(0, 0)
effect.setBlurRadius(20)
effect.setColor(self.border_color)
self.setGraphicsEffect(effect)
If you want to run a task after a while you should not use time.sleep() because as it blocks the GUI causing it not to behave correctly, the best option is to use QTimer::singleShot().
On the other hand I see that you are passing a function so that it executes a task when it is clicked, that is not scalable, in Qt the correct thing is to create a signal and the advantage is that you can connect the same signal to several functions without coupling , that is, the one that emits the signal must not know in advance who will receive the signal.
I recommend taking values for defects for the arguments, I have taken the time to give an improvement to your code:
from PySide2 import QtCore, QtGui, QtWidgets
class ClickableLabel(QtWidgets.QLabel):
clicked = QtCore.Signal()
def __init__(self, pic_path="", width=80, height=30, border_color=QtGui.QColor("lightblue"), parent=None):
super(ClickableLabel, self).__init__(parent)
self.setPixmap(QtGui.QPixmap(pic_path))
self.setFixedSize(width, height)
self.setMouseTracking(True)
self.border_color = QtGui.QColor(border_color)
self.effect = QtWidgets.QGraphicsDropShadowEffect(self,
offset=QtCore.QPointF(0, 0),
blurRadius=20,
color=self.border_color)
self.setGraphicsEffect(self.effect)
self.disable_effect()
def mouseMoveEvent(self, event):
self.setStyleSheet("border: 1px solid {};".format(self.border_color.name()))
super(ClickableLabel, self).mouseMoveEvent(event)
def leaveEvent(self, event):
self.setStyleSheet("border: None")
super(ClickableLabel, self).leaveEvent(event)
def mousePressEvent(self, event):
self.clicked.emit()
self.effect.setEnabled(True)
QtCore.QTimer.singleShot(500, self.disable_effect)
super(ClickableLabel, self).mousePressEvent(event)
#QtCore.Slot()
def disable_effect(self):
self.effect.setEnabled(False)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
def on_click():
print("click")
w = ClickableLabel(pic_path="heart.png")
w.clicked.connect(on_click)
w.show()
sys.exit(app.exec_())
I want to get mouse position in my pyside2 application.(not desktop mouse position that QCursor gives) and I tried two way. Bellow is my code.
import sys
from PySide2 import QtGui, QtWidgets, QtCore
class Palette(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
def mousePressEvent(self, event):
print(event.pos()) # always return (0,0)
print(QtWidgets.QWidget.mapToGlobal(QtCore.QPoint(0, 0))) #makes parameter type error
print(QtWidgets.QWidget.mapToGlobal(QtWidgets.QWidget)) # makes parameter type error
print(QtWidgets.QWidget.mapToGlobal(QtWidgets.QWidget.pos())) # makes parameter type error
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
palette = Palette(self)
view = QtWidgets.QGraphicsView(palette, self)
view.resize(500, 500)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.resize(500, 500)
main_window.show()
app.exec_()
I very wonder how i can get my mouse pos...
From what I understand you want to get the position on the window if you click anywhere in a widget.
To solve the problem, the logic is as follows:
Get the mouse position with respect to the widget
Convert that position to a global position, that is, with respect to the screen.
Convert that global position to a position relative to the window.
For the first step if mousePressEvent() is used, event.pos() returns the position relative to the widget.
For the second step you must convert that position relative to the widget to global with mapToGlobal().
And for the third step mapFromGlobal() of window is used.
def mousePressEvent(self, event):
p = event.pos() # relative to widget
gp = self.mapToGlobal(p) # relative to screen
rw = self.window().mapFromGlobal(gp) # relative to window
print("position relative to window: ", rw)
super(Widget, self).mousePressEvent(event)
Update:
The QGraphicsScene is not a widget, it is not a visual element, although it is part of the representation of a visual element: the QGraphicsView. For you to understand I will explain you with an analogy, let's say that there is a cameraman recording a scene, in that example the QGraphicsScene is the scene and the QGraphicsView is what the camera records, that is, it shows a piece of the QGraphicsScene, so there could be another cameraman recording the scene from another point, so it would show the same scene from another perspective, so the position of the scene depends on the camera, so if your current question would be equivalent to saying which is the position of the point P respect to the camera i-th, and that from the scene is impossible, you should get it from the camera.
So in conclusion you should not use QGraphicsScene but QGraphicsView, the following solutions implement the same logic using 2 different methods:
1. Creating a custom class of QGraphicsView:
import sys
from PySide2 import QtGui, QtWidgets, QtCore
class GraphicsView(QtWidgets.QGraphicsView):
def mousePressEvent(self, event):
p = event.pos() # relative to widget
gp = self.mapToGlobal(p) # relative to screen
rw = self.window().mapFromGlobal(gp) # relative to window
print("position relative to window: ", rw)
super(GraphicsView, self).mousePressEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
scene = QtWidgets.QGraphicsScene(self)
view = GraphicsView(scene, self)
self.setCentralWidget(view)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.resize(500, 500)
main_window.show()
sys.exit(app.exec_())
2. Using eventfilter:
import sys
from PySide2 import QtGui, QtWidgets, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
scene = QtWidgets.QGraphicsScene(self)
self._view = QtWidgets.QGraphicsView(scene, self)
self.setCentralWidget(self._view)
self._view.installEventFilter(self)
def eventFilter(self, obj, event):
if obj is self._view and event.type() == QtCore.QEvent.MouseButtonPress:
p = event.pos() # relative to widget
gp = self.mapToGlobal(p) # relative to screen
rw = self.window().mapFromGlobal(gp) # relative to window
print("position relative to window: ", rw)
return super(MainWindow, self).eventFilter(obj, event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.resize(500, 500)
main_window.show()
sys.exit(app.exec_())
On the other hand mapToGlobal() is a method that must be called by an instance, when you use QtWidgets.QWidget.mapToGlobal() there is no instance, my question is, what widget do you have the position? the position you have with respect to self, so you must use self.mapToGlobal() , that works for the objects belonging to a class that inherit from QWidget as QGraphicsView, but not in QGraphicsScene since it does not inherit from QWidget, it is not a widget as indicated in the lines above.
I have recently found a more universal way of getting the cursor position, if you don't want to go through sub classing and events.
# get cursor position
cursor_position = QtGui.QCursor.pos()
print cursor_position
### Returns PySide2.QtCore.QPoint(3289, 296)
My actual application is much more complicated than this, but the example below sums up the majority of my problem. I have multiple QLabels that I've subclassed to make them clickable. The labels display 16x16 images which requires a process of loading the images via Pillow, converting them to ImageQt objects, and then setting the pixmap of the label. In the example, I have 3 clickable QLabels that run the print_something function each time I click on them. My goal is to be able to hold the mouse down, and for each label I hover over, the function gets called. Any pointers would be great.
from PyQt5 import QtCore, QtWidgets, QtGui
from PIL import Image
from PIL.ImageQt import ImageQt
import sys
class ClickableLabel(QtWidgets.QLabel):
def __init__(self):
super().__init__()
clicked = QtCore.pyqtSignal()
def mousePressEvent(self, ev):
if app.mouseButtons() & QtCore.Qt.LeftButton:
self.clicked.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central_widget = QtWidgets.QWidget()
self.setFixedSize(300, 300)
image = Image.open("16x16image.png")
image_imageqt = ImageQt(image)
hbox = QtWidgets.QHBoxLayout()
hbox.setSpacing(0)
hbox.addStretch()
label01 = ClickableLabel()
label01.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
label01.clicked.connect(self.print_something)
hbox.addWidget(label01)
label02 = ClickableLabel()
label02.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
label02.clicked.connect(self.print_something)
hbox.addWidget(label02)
label03 = ClickableLabel()
label03.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
label03.clicked.connect(self.print_something)
hbox.addWidget(label03)
hbox.addStretch()
central_widget.setLayout(hbox)
self.setCentralWidget(central_widget)
def print_something(self):
print("Printing something..")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
The cause of the problem is stated in the docs for QMouseEvent:
Qt automatically grabs the mouse when a mouse button is pressed inside
a widget; the widget will continue to receive mouse events until the
last mouse button is released.
It does not look like there is a simple way around this, so something hackish will be required. One idea is to initiate a fake drag and then use dragEnterEvent instead of enterEvent. Something like this should probably work:
class ClickableLabel(QtWidgets.QLabel):
clicked = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.dragstart = None
def mousePressEvent(self, event):
if event.buttons() & QtCore.Qt.LeftButton:
self.dragstart = event.pos()
self.clicked.emit()
def mouseReleaseEvent(self, event):
self.dragstart = None
def mouseMoveEvent(self, event):
if (self.dragstart is not None and
event.buttons() & QtCore.Qt.LeftButton and
(event.pos() - self.dragstart).manhattanLength() >
QtWidgets.qApp.startDragDistance()):
self.dragstart = None
drag = QtGui.QDrag(self)
drag.setMimeData(QtCore.QMimeData())
drag.exec_(QtCore.Qt.LinkAction)
def dragEnterEvent(self, event):
event.acceptProposedAction()
if event.source() is not self:
self.clicked.emit()