I'm using Python 2.7 and PyQt4. I am trying to have a half-circle object that is a QGraphicsItem. I want to be able to move it using the mouse, by clicking and dragging. I can create the object and move it around with the mouse by setting the flag ItemIsMovable. Now the half-circle moves around freely but I want it to move just around the fixed central point. It is difficult to describe, but it should be something similar to a dial. How can I accomplish this?
you can use QGraphicsItem::mouseMoveEvent event to track item's movements within the scene and correct its position once it's moved off the restricted area. Pls, check if an example below would work for you:
import sys
from PyQt4 import QtGui, QtCore
class TestEclipseItem(QtGui.QGraphicsEllipseItem):
def __init__(self, parent=None):
QtGui.QGraphicsPixmapItem.__init__(self, parent)
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
# set move restriction rect for the item
self.move_restrict_rect = QtCore.QRectF(20, 20, 200, 200)
# set item's rectangle
self.setRect(QtCore.QRectF(50, 50, 50, 50))
def mouseMoveEvent(self, event):
# check of mouse moved within the restricted area for the item
if self.move_restrict_rect.contains(event.scenePos()):
QtGui.QGraphicsEllipseItem.mouseMoveEvent(self, event)
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
scene = QtGui.QGraphicsScene(-50, -50, 600, 600)
ellipseItem = TestEclipseItem()
scene.addItem(ellipseItem)
view = QtGui.QGraphicsView()
view.setScene(scene)
view.setGeometry(QtCore.QRect(0, 0, 400, 200))
self.setCentralWidget(view)
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
hope this helps, regards
Related
I have a QPaintEvent override for a custom widget that has a fixed size set. This fixed size can change per instance but in this simple example, ive set it. however the PaintEvent doesn't take it into account so when the users scrolls to the right the rectangle shouldn't paint rounded corners since the widget extends past the visible viewport. How do i fix this?
Full widget painted correctly...
When i resize dialog and scroll right, you'll see rounded corners appear on the left side... when it should NOT.
They should look like this...
Code
import os
import sys
from PySide2 import QtGui, QtWidgets, QtCore, QtSvg
class Card(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Card, self).__init__(parent=parent)
self.label = QtWidgets.QLabel('Help This Paint Event Is Broken')
self.label.setFixedHeight(40)
self.label.setFixedWidth(300)
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.mainLayout.addWidget(self.label)
# overrides
def paintEvent(self, event):
painter = QtGui.QPainter()
painter.begin(self)
painter.setOpacity(1.0)
painter.setRenderHints(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QColor(0, 0, 0, 128))
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(QtGui.QColor('#F44336'))
painter.drawRoundedRect(event.rect(), 12, 12)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.end()
class ListViewExample(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ListViewExample, self).__init__(parent)
self.resize(200,200)
self.listView = QtWidgets.QListWidget()
self.listView.setSpacing(10)
self.listView.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.listView.verticalScrollBar().setSingleStep(10)
# layout
self.mainLayout = QtWidgets.QVBoxLayout()
self.mainLayout.setContentsMargins(0,0,0,0)
self.mainLayout.addWidget(self.listView)
self.setLayout(self.mainLayout)
for x in range(50):
wgt = Card()
self.appendItem(wgt)
def appendItem(self, widget):
lwi = QtWidgets.QListWidgetItem()
lwi.setSizeHint(widget.sizeHint())
self.listView.addItem(lwi)
self.listView.setItemWidget(lwi, widget)
################################################################################
# Widgets
################################################################################
def unitTest_CardDelegate():
app = QtWidgets.QApplication(sys.argv)
window = ListViewExample()
window.show()
app.exec_()
if __name__ == '__main__':
pass
unitTest_CardDelegate()
QPaintEvent::rect() returns the visible rectangle, not the rectangle of the widget itself, so you observe this behavior. The solution is:
painter.drawRoundedRect(self.rect(), 12, 12)
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)
I'm very new to PySide/PyQt environment. I'm trying to make a menu of buttons on top and assign a task to each so that when they are clicked a function draws a painting on the central window. But I also want to make the button change when they are clicked.
I think this might be an straighforward problem to solve if I use QPushButton, but my buttons are images and I'm using the method suggested HERE and use QAbstractButton to create them.
It is mentioned there that
You can add second pixmap and draw it only when the mouse pointer is
hover over button.
And I'm trying to do exactly that. My question is this:
what are possible ways to achieve this? Are the same methods in QPushButtons applicable here? If so, are there any examples of it somewhere?
Here is a snippet of my code:
import sys
from PySide import QtGui, QtCore
BACKGROUND_COLOR = '#808080'
ICON_PATH_ACTIVE = 'icons/activ'
ICON_PATH_PASSIVE = 'icons/pasiv'
class MainWindow(QtGui.QMainWindow):
def __init__(self, app=None):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
dockwidget = QtGui.QWidget()
self.setGeometry(200, 200, 400, 300)
hbox = QtGui.QHBoxLayout()
1_button = PicButton(QtGui.QPixmap("icons/pasiv/1.png"))
2_button = PicButton(QtGui.QPixmap("icons/pasiv/2.png"))
3_button = PicButton(QtGui.QPixmap("icons/pasiv/3.png"))
hbox.addWidget(1_button)
hbox.addWidget(2_button)
hbox.addWidget(3_button)
vbox = QtGui.QVBoxLayout()
vbox.addLayout(hbox)
vbox.setAlignment(hbox, QtCore.Qt.AlignTop)
dockwidget.setLayout(vbox)
self.setCentralWidget(dockwidget)
class PicButton(QtGui.QAbstractButton):
def __init__(self, pixmap, parent=None):
super(PicButton, self).__init__(parent)
self.pixmap = pixmap
self.setFixedSize(100, 100)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawPixmap(event.rect(), self.pixmap)
def main():
app = QtGui.QApplication(sys.argv)
central = MainWindow()
central.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Thank you.
Use a regular QPushButton with an icon.
iplay = QtGui.QIcon("path/play_icon.png")
ipause = QtGui.QIcon("path/pause_icon.png")
btn = QtGui.QPushButton(ipause, "", None)
def toggle_play():
if btn.icon() == ipause:
btn.setIcon(iplay)
# Do Pause Action
else:
btn.setIcon(ipause)
# Do Play Action
btn.clicked.connect(toggle_play)
btn.show()
If you want hover functionality then you will have to subclass the QPushButton
class MyButton(QtGui.QPushButton):
custom_click_signal = QtCore.Signal()
def enterEvent(self, event):
super().enterEvent(event)
# Change icon hove image here
def leaveEvent(self, event):
super().leaveEvent(event)
# Change icon back to original image here.
def mousePressEvent(self, event):
super().mousePressEvent(event)
self.custom_click_signal.emit()
# connect to signal btn.custom_click_signal.connect(method)
Icons are probably the easiest way instead of manually managing the paint event. There are also mousePressEvent and mouseReleaseEvents if you want the icon to change for someone holding the button down.
I have a GraphicScene inside a QGraphicView populated with many rectangles (items). I would like each rectangle to respond to the mouse click but I'm not able to find the hook to attach the event handler to the correct object and to get the event propagated to it.
I attached an event handler to the scene:
scene.event = myfunction
and it worked (it was firing every event) but I was unable to attach the same function to one of its children. Can you give me an insight on where to search for such an entry point?
So - I'm not really sure what you're doing there, but I can't think of anything in PyQt where you should be mapping a custom function directly to a scene's event method.
Do you have an actual example?
If you're doing:
scene.mousePressEvent = my_mouse_function
Then that is not how you want to do that.
You can look into using an event filters (http://doc.qt.nokia.com/4.7-snapshot/eventsandfilters.html#event-filters).
Best way to get what you want is to subclass the QGraphicsItem (whichever one you are using - QGraphicsRectItem, QGraphicsPathItem, etc.) and overload the mousePressEvent method on it.
http://doc.qt.nokia.com/4.7-snapshot/qgraphicsitem.html#mousePressEvent
For instance:
from PyQt4.QtGui import QGraphicsRectItem
class MyItem(QGraphicsRectItem):
def mousePressEvent(self, event):
super(MyItem, self).mousePressEvent(event)
print 'overloaded'
scene.addItem(MyItem())
Either subclass the view, scene, item etc and reimplement mousePressEvent and/or mouseReleaseEvent; or install an event filter on those items.
For an example that uses an event filter on a scene, see this answer.
Here's a demo which reimplements mouseReleaseEvent on the view:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.view = View(self)
self.label = QtGui.QLabel(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.label)
class View(QtGui.QGraphicsView):
def __init__(self, parent):
QtGui.QGraphicsView.__init__(self, parent)
self.setScene(QtGui.QGraphicsScene(self))
for index, name in enumerate('One Two Three Four Five'.split()):
item = QtGui.QGraphicsRectItem(
index * 60, index * 60, 50, 50)
item.setData(0, name)
self.scene().addItem(item)
def mouseReleaseEvent(self, event):
pos = event.pos()
item = self.itemAt(pos)
if item is not None:
text = 'Rectangle <b>%s</b>' % item.data(0).toString()
else:
text = 'No Rectangle (%d, %d)' % (pos.x(), pos.y())
self.parent().label.setText(text)
QtGui.QGraphicsView.mouseReleaseEvent(self, event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(400, 400)
window.show()
sys.exit(app.exec_())
just a simple question: I'm using pyqt4 to render a simple window. Here's the code, I post the whole thing so it easier to explain.
from PyQt4 import QtGui, QtCore, Qt
import time
import math
class FenixGui(QtGui.QWidget):
def __init__(self):
super(FenixGui, self).__init__()
# setting layout type
hboxlayout = QtGui.QHBoxLayout(self)
self.setLayout(hboxlayout)
# hiding title bar
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
# setting window size and position
self.setGeometry(200, 200, 862, 560)
self.setAttribute(Qt.Qt.WA_TranslucentBackground)
self.setAutoFillBackground(False)
# creating background window label
backgroundpixmap = QtGui.QPixmap("fenixbackground.png")
self.background = QtGui.QLabel(self)
self.background.setPixmap(backgroundpixmap)
self.background.setGeometry(0, 0, 862, 560)
# fenix logo
logopixmap = QtGui.QPixmap("fenixlogo.png")
self.logo = QtGui.QLabel(self)
self.logo.setPixmap(logopixmap)
self.logo.setGeometry(100, 100, 400, 150)
def main():
app = QtGui.QApplication([])
exm = FenixGui()
exm.show()
app.exec_()
if __name__ == '__main__':
main()
Now, you see that I put a background label in my window. I would like that the window could be dragged around the screen by dragging this label. I mean: you click on the label, you drag the label, and the whole window comes around the screen. Is this possible? I accept non-elegant ways as well, because as you can see I hid the title bar so it would be impossible to drag the window if I don't make it draggable via the background label.
Hope I explained my problem properly
Thank you very much!!
Matteo Monti
You can override mousePressEvent() and mouseMoveEvent() to get the location of the mouse cursor and move your widget to that location. mousePressEvent will give you the offset from the cursor position to the top left corner of your widget, and then you can calculate what the new position of the top left corner should be. You can add these methods to your FenixGui class.
def mousePressEvent(self, event):
self.offset = event.pos()
def mouseMoveEvent(self, event):
x=event.globalX()
y=event.globalY()
x_w = self.offset.x()
y_w = self.offset.y()
self.move(x-x_w, y-y_w)