Bind canvas to window when canvas is dragged in pyqt - python

I have an app that sets a matplotlib graph to a FigurCanvas and then adds the FigurCanvas to my AppWindow, I have it set up so the graph is draggable. However when I drag the graph the window it is contained in stays where it is so the graph get dragged off the window. Is there a way to bind the two together so when the graph is moved the window stays with it? Here is the code.
from PyQt4 import QtCore
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class GraphCanvas(FigureCanvas):
def __init__(self):
# The window
self.fig = Figure(figsize=(5, 5), dpi=100)
self.ax1 = self.fig.add_subplot(111)
self.ax1.plot([1,2,3], [1,2,3], linewidth=2, color="#c6463d", label="line1")
FigureCanvas.__init__(self, self.fig)
# drag properties
self.draggable = True
self.dragging_threshold = 5
self.__mousePressPos = None
self.__mouseMovePos = None
def mousePressEvent(self, event):
if self.draggable and event.button() == QtCore.Qt.LeftButton:
self.__mousePressPos = event.globalPos() # global
self.__mouseMovePos = event.globalPos() - self.pos() # local
super(GraphCanvas, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.draggable and event.buttons() & QtCore.Qt.LeftButton:
globalPos = event.globalPos()
moved = globalPos - self.__mousePressPos
if moved.manhattanLength() > self.dragging_threshold:
# move when user drag window more than dragging_threshould
diff = globalPos - self.__mouseMovePos
self.move(diff)
self.__mouseMovePos = globalPos - self.pos()
super(GraphCanvas, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self.__mousePressPos is not None:
if event.button() == QtCore.Qt.LeftButton:
moved = event.globalPos() - self.__mousePressPos
if moved.manhattanLength() > self.dragging_threshold:
# do not call click event or so on
event.ignore()
self.__mousePressPos = None
super(GraphCanvas, self).mouseReleaseEvent(event)
''' End Class '''
class AppWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(AppWindow, self).__init__(parent)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
layout = QtGui.QVBoxLayout(self)
cpu_canvas = GraphCanvas()
layout.addWidget(cpu_canvas)
''' End Class'''
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
main = AppWindow()
main.show()
sys.exit(app.exec_())

If you want to drag the AppWindow you should register the dragging in this class, instead of the Figure canvas.
Inside figure canvas you can then route the drag events to the AppWindow.
The following should work for you, where I did not change much of the code, just rearanged it, added the parent argument to GraphCanvas and let the dragging functions call their parent's functions.
from PyQt4 import QtCore
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class GraphCanvas(FigureCanvas):
def __init__(self, parent = None):
self.parent = parent
# The window
self.fig = Figure(figsize=(5, 5), dpi=100)
self.ax1 = self.fig.add_subplot(111)
self.ax1.plot([1,2,3], [1,2,3], linewidth=2, color="#c6463d", label="line1")
FigureCanvas.__init__(self, self.fig)
def mousePressEvent(self, event):
self.parent.mousePressEvent(event)
def mouseMoveEvent(self,event):
self.parent.mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.parent.mouseReleaseEvent(event)
''' End Class '''
class AppWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(AppWindow, self).__init__(parent)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
layout = QtGui.QVBoxLayout(self)
cpu_canvas = GraphCanvas(self)
layout.addWidget(cpu_canvas)
self.draggable = True
self.dragging_threshold = 5
self.__mousePressPos = None
self.__mouseMovePos = None
def mousePressEvent(self, event):
if self.draggable and event.button() == QtCore.Qt.LeftButton:
self.__mousePressPos = event.globalPos() # global
self.__mouseMovePos = event.globalPos() - self.pos() # local
super(AppWindow, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.draggable and event.buttons() & QtCore.Qt.LeftButton:
globalPos = event.globalPos()
moved = globalPos - self.__mousePressPos
if moved.manhattanLength() > self.dragging_threshold:
# move when user drag window more than dragging_threshould
diff = globalPos - self.__mouseMovePos
self.move(diff)
self.__mouseMovePos = globalPos - self.pos()
super(AppWindow, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self.__mousePressPos is not None:
if event.button() == QtCore.Qt.LeftButton:
moved = event.globalPos() - self.__mousePressPos
if moved.manhattanLength() > self.dragging_threshold:
# do not call click event or so on
event.ignore()
self.__mousePressPos = None
super(AppWindow, self).mouseReleaseEvent(event)
''' End Class'''
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
main = AppWindow()
main.show()
sys.exit(app.exec_())

Related

Pyqt: custom frame doesn't detect mouse click under mouseMoveEvent

I have created a custom Frame for a Qgraphicswidget. I have come across two problems, First, one being that clicks are not being detected under MouseMoveEvent, that is event.button() always returns 0 even if there is a mouse click. Second, is that my setCursor() doesn't change the cursor. Here is my code under the custom frame class.
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt, QRectF, QEvent, QPoint
from PyQt5.QtGui import QPen, QColor, QPainter, QBrush, qRgb, QPolygon
from PyQt5.QtWidgets import *
import sys
class Frame(QFrame):
def __init__(self, parent=None, option=[], margin=0):
super(Frame, self).__init__()
self.parent = parent
self._triangle = QPolygon()
self.options = option
self._margin = margin
self.start_pos = None
# self.parent.setViewport(parent)
self.setStyleSheet('background-color: lightblue')
self.setMouseTracking(True)
self.installEventFilter(self)
self.show()
def update_option(self, option):
self.options = option
def paintEvent(self, event):
super().paintEvent(event)
qp = QPainter(self)
qp.setPen(Qt.white)
qp.setBrush(Qt.gray)
qp.drawPolygon(self._triangle)
def _recalculate_triangle(self):
p = QPoint(self.width() - 20, self.height() - 10)
q = QPoint(self.width() - 10, self.height() - 20)
r = QPoint(self.width() - 10, self.height() - 10)
self._triangle = QPolygon([p, q, r])
self.update()
def resizeEvent(self, event):
self._recalculate_triangle()
super().resizeEvent(event)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
if event.button() == Qt.LeftButton and self._triangle.containsPoint(
event.pos(), Qt.OddEvenFill
):
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
self.start_pos = event.pos()
# print(self.start_pos)
else:
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
else:
self.parent.viewport().unsetCursor()
self.start_pos = None
if event.button() == Qt.LeftButton:
if event.button() == QtCore.Qt.LeftButton and self.start_pos is not None:
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
delta = event.pos() - self.start_pos
self.n_resize(self.width()+delta.x(), self.height()+delta.y())
self.start_pos = event.pos()
elif not self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mouseReleaseEvent(event)
def n_resize(self, width, height):
self.resize(width, height)
if __name__ == '__main__':
q = QApplication(sys.argv)
a = Frame()
sys.exit(q.exec())
I have also tried using eventfilter but of no use.
EDIT:
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPen, QColor, QPainter, QBrush, QPolygon
from PyQt5.QtWidgets import *
import sys
class graphLayout(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self.scene = QGraphicsScene()
self.lines = []
self.draw_grid()
self.set_opacity(0.3)
widget = QGraphicsProxyWidget()
t = stack(self)
t.setFlag(QGraphicsItem.ItemIsMovable)
t.resize(340, 330)
self.scene.addItem(t)
self.setScene(self.scene)
self.show()
def create_texture(self):
image = QtGui.QImage(QtCore.QSize(30, 30), QtGui.QImage.Format_RGBA64)
pen = QPen()
pen.setColor(QColor(189, 190, 191))
pen.setWidth(2)
painter = QtGui.QPainter(image)
painter.setPen(pen)
painter.drawRect(image.rect())
painter.end()
return image
def draw_grid(self):
texture = self.create_texture()
brush = QBrush()
# brush.setColor(QColor('#999'))
brush.setTextureImage(texture) # Grid pattern.
self.scene.setBackgroundBrush(brush)
borderColor = Qt.black
fillColor = QColor('#DDD')
def set_visible(self, visible=True):
for line in self.lines:
line.setVisible(visible)
def delete_grid(self):
for line in self.lines:
self.scene.removeItem(line)
del self.lines[:]
def set_opacity(self, opacity):
for line in self.lines:
line.setOpacity(opacity)
def wheelEvent(self, event):
if event.modifiers() == Qt.ControlModifier:
delta = event.angleDelta().y()
if delta > 0:
self.on_zoom_in()
elif delta < 0:
self.on_zoom_out()
super(graphLayout, self).wheelEvent(event)
def mousePressEvent(self, event):
if event.button() == Qt.MidButton:
self.setCursor(Qt.OpenHandCursor)
self.mousepos = event.localPos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
# This helps to pan the area
if event.buttons() == Qt.MidButton:
delta = event.localPos() - self.mousepos
h = self.horizontalScrollBar().value()
v = self.verticalScrollBar().value()
self.horizontalScrollBar().setValue(int(h - delta.x()))
self.verticalScrollBar().setValue(int(v - delta.y()))
self.mousepos = event.localPos()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.unsetCursor()
self.mousepos = event.localPos()
super().mouseReleaseEvent(event)
def on_zoom_in(self):
if self.transform().m11() < 3.375:
self.setTransformationAnchor(self.AnchorUnderMouse)
self.scale(1.5, 1.5)
def on_zoom_out(self):
if self.transform().m11() > 0.7:
self.setTransformationAnchor(self.AnchorUnderMouse)
self.scale(1.0 / 1.5, 1.0 / 1.5)
class stack(QGraphicsWidget):
_margin = 0
def __init__(self, parent=None):
super().__init__()
self.options = []
self.gridlayout = parent
graphic_layout = QGraphicsLinearLayout(Qt.Vertical, self)
self.width, self.height = 10, 10
self.outer_container = Frame(parent, self.options, self._margin)
self.outer_container.setContentsMargins(0, 0, 0, 0)
self.setParent(self.outer_container)
layout = QVBoxLayout()
self.headerLayout = QGridLayout()
self.headerLayout.setContentsMargins(2, 0, 0, 0)
self.top_bar = QFrame()
self.top_bar.setFrameShape(QFrame.StyledPanel)
self.top_bar.setLayout(self.headerLayout)
self.top_bar.setContentsMargins(0, 0, 0, 0)
self.top_bar.setMaximumHeight(30)
# self.contentLayout = QVBoxLayout()
self.contentLayout = QFormLayout()
self.contentLayout.setContentsMargins(10, 10, 10, 10)
self.contentLayout.setSpacing(5)
layout.addWidget(self.top_bar)
layout.addLayout(self.contentLayout)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
self.outer_container.setContentsMargins(0, 0, 0, 0)
self.setContentsMargins(0, 0, 0, 0)
self.setMaximumSize(400, 800)
self.outer_container.setLayout(layout)
widget = QGraphicsProxyWidget()
widget.setWidget(self.outer_container)
# todo: figure out a way to add top_bar widget
graphic_layout.addItem(widget)
graphic_layout.setSpacing(0)
graphic_layout.setContentsMargins(0, 0, 0, 0)
# widget move and resize note: don't touch any of these
self.__mouseMovePos = None
self._triangle = QPolygon()
self.start_pos = None
def addHeaderWidget(self, widget=None, column=0, bg_color='green'):
self.top_bar.setStyleSheet(f'background-color:{bg_color};')
self.headerLayout.addWidget(widget, 0, column)
class Frame(QFrame):
def __init__(self, parent=None, option=[], margin=0):
super(Frame, self).__init__()
self.parent = parent
self._triangle = QPolygon()
self.options = option
self._margin = margin
self.start_pos = None
# self.parent.setViewport(parent)
self.setStyleSheet('background-color: lightblue')
self.setMouseTracking(True)
self.installEventFilter(self)
self.show()
def update_option(self, option):
self.options = option
def paintEvent(self, event):
super().paintEvent(event)
qp = QPainter(self)
qp.setPen(Qt.white)
qp.setBrush(Qt.gray)
qp.drawPolygon(self._triangle)
def _recalculate_triangle(self):
p = QPoint(self.width() - 20, self.height() - 10)
q = QPoint(self.width() - 10, self.height() - 20)
r = QPoint(self.width() - 10, self.height() - 10)
self._triangle = QPolygon([p, q, r])
self.update()
def resizeEvent(self, event):
self._recalculate_triangle()
super().resizeEvent(event)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
if event.button() == Qt.LeftButton and self._triangle.containsPoint(
event.pos(), Qt.OddEvenFill
):
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
self.start_pos = event.pos()
# print(self.start_pos)
else:
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
else:
self.parent.viewport().unsetCursor()
self.start_pos = None
if event.button() == Qt.LeftButton:
if event.button() == QtCore.Qt.LeftButton and self.start_pos is not None:
self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
delta = event.pos() - self.start_pos
self.n_resize(self.width()+delta.x(), self.height()+delta.y())
self.start_pos = event.pos()
elif not self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.parent.viewport().unsetCursor()
self.start_pos = None
super().mouseReleaseEvent(event)
def n_resize(self, width, height):
self.resize(width, height)
if __name__ == '__main__':
q = QApplication(sys.argv)
a = graphLayout()
sys.exit(q.exec())
There are various issues on your code, but the base problems are:
mouse button state cannot be retrieved by event.button() in a MouseMove event, and event.buttons() should be used instead: the difference is clear: button() shows the buttons that generate the event (and a mouse move event is obviously not generated by any button), buttons() shows the button state when the event is generated;
events that are not explicitly managed by an object are always propagated to its parent(s), which means that your mouse movements are also possibly processed by the parent widget, then the graphics proxy, the scene, the viewport, the view, etc, and that up to the top level window, until one of the previous objects actually returns True from event() or an event filter; in your case it results in moving the graphics item, since you enabled the ItemIsMovable flag.
I don't know why the cursor is not actually set, but frankly your code is so convoluted that I really cannot find the reason.
Since what you're actually looking for is a way to resize the widget, I suggest you another solution.
While implementing a resizing with custom painting is certainly feasible, in most cases it's well enough to use a QSizeGrip (as already suggested to you in another post), which is a widget that allows resizing top-level windows and is automatically able to understand which "corner" use for the resizing based on its position. Remember that the parent of the QSizeGrip is very important, because it uses it to understand which is its top level window, and, in this case, the "container frame", even if it's in a QGraphicsScene.
Note that QSizeGrip should not be added to a layout, and it should always be manually moved according to its corner position and the size of its parent (unless it's placed on the top left corner), and since you already need custom painting, it's better to subclass it.
from PyQt5 import QtCore, QtGui, QtWidgets
class SizeGrip(QtWidgets.QSizeGrip):
def __init__(self, parent):
super().__init__(parent)
parent.installEventFilter(self)
self.setFixedSize(30, 30)
self.polygon = QtGui.QPolygon([
QtCore.QPoint(10, 20),
QtCore.QPoint(20, 10),
QtCore.QPoint(20, 20),
])
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.Resize:
geo = self.rect()
geo.moveBottomRight(source.rect().bottomRight())
self.setGeometry(geo)
return super().eventFilter(source, event)
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setPen(QtCore.Qt.white)
qp.setBrush(QtCore.Qt.gray)
qp.drawPolygon(self.polygon)
class Container(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sizeGrip = SizeGrip(self)
self.startPos = None
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(6, 6, 6, 30)
self.setStyleSheet('''
Container {
background: lightblue;
border: 0px;
border-radius: 4px;
}
''')
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.startPos = event.pos()
def mouseMoveEvent(self, event):
if self.startPos:
self.move(self.pos() + (event.pos() - self.startPos))
def mouseReleaseEvent(self, event):
self.startPos = None
class GraphicsRoundedFrame(QtWidgets.QGraphicsProxyWidget):
def __init__(self):
super().__init__()
self.container = Container()
self.setWidget(self.container)
def addWidget(self, widget):
self.container.layout().addWidget(widget)
def paint(self, qp, opt, widget):
qp.save()
p = QtGui.QPainterPath()
p.addRoundedRect(self.boundingRect().adjusted(0, 0, -.5, -.5), 4, 4)
qp.setClipPath(p)
super().paint(qp, opt, widget)
qp.restore()
class View(QtWidgets.QGraphicsView):
def __init__(self):
super().__init__()
scene = QtWidgets.QGraphicsScene()
self.setScene(scene)
self.setRenderHints(QtGui.QPainter.Antialiasing)
scene.setSceneRect(0, 0, 1024, 768)
texture = QtGui.QImage(30, 30, QtGui.QImage.Format_ARGB32)
qp = QtGui.QPainter(texture)
qp.setBrush(QtCore.Qt.white)
qp.setPen(QtGui.QPen(QtGui.QColor(189, 190, 191), 2))
qp.drawRect(texture.rect())
qp.end()
scene.setBackgroundBrush(QtGui.QBrush(texture))
testFrame = GraphicsRoundedFrame()
scene.addItem(testFrame)
testFrame.addWidget(QtWidgets.QLabel('I am a label'))
testFrame.addWidget(QtWidgets.QPushButton('I am a button'))
import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())

Insert Image into QGridLayout and Draw on top of image in PyQt5

I'm pretty new to PyQt and am trying to make an application with a QPixmap on the left, which can be drawn on, and a QTextEdit on the right (for a simple OCR GUI).
I looked at:
PyQt5 Image and QGridlayout
but I couldn't connect it with the code below (I'm losing my hair with all the head scratching!!)
When I try adapting the following code, what I get is a QMainWindow with the QPixmap as the background which can be drawn on with the mouse and a second occurance of the QPixmap in it's correct position, which can not be drawn on. Can someone tell me what I'm doing wrong?
Thank you very much!
# https://stackoverflow.com/questions/51475306/
import sys
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QMainWindow, QApplication,QGridLayout, QLabel, QWidget, QTextEdit
from PyQt5.QtGui import QPixmap, QPainter, QPen
class Menu(QMainWindow):
def __init__(self):
super().__init__()
self.drawing = False
self.lastPoint = QPoint()
self.image = QPixmap("S3.png")
self.setGeometry(100, 100, 500, 300)
self.resize(self.image.width(), self.image.height())
layout = QGridLayout()
# Add a QTextEdit box
self.edit = QTextEdit()
layout.addWidget(self.edit, 0, 0, 10, 20)
# This:
# https://stackoverflow.com/questions/52616553
# indicates that a QPixmap must be put into a label to insert into a QGridLayout
self.label = QLabel()
self.label.setPixmap(self.image)
layout.addWidget(self.label, 10, 20, 10, 20)
# https://stackoverflow.com/questions/37304684/
self.widget = QWidget()
self.widget.setLayout(layout)
self.setCentralWidget(self.widget)
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.image)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = True
self.lastPoint = event.pos()
print(self.lastPoint)
def mouseMoveEvent(self, event):
if event.buttons() and Qt.LeftButton and self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(Qt.red, 3, Qt.SolidLine))
painter.drawLine(self.lastPoint, event.pos())
print(self.lastPoint,event.pos())
self.lastPoint = event.pos()
self.update()
def mouseReleaseEvent(self, event):
if event.button == Qt.LeftButton:
self.drawing = False
if __name__ == '__main__':
app = QApplication(sys.argv)
mainMenu = Menu()
sys.exit(app.exec_())
Each widget must fulfill a specific task, so I have created a widget that only has the painted function, the main widget works as a container for the painting widget and the QTextEdit.
from PyQt5 import QtCore, QtGui, QtWidgets
class Label(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Label, self).__init__(parent)
self.image = QtGui.QPixmap("S3.png")
self.drawing = False
self.lastPoint = QtCore.QPoint()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawPixmap(QtCore.QPoint(), self.image)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.drawing = True
self.lastPoint = event.pos()
def mouseMoveEvent(self, event):
if event.buttons() and QtCore.Qt.LeftButton and self.drawing:
painter = QtGui.QPainter(self.image)
painter.setPen(QtGui.QPen(QtCore.Qt.red, 3, QtCore.Qt.SolidLine))
painter.drawLine(self.lastPoint, event.pos())
self.lastPoint = event.pos()
self.update()
def mouseReleaseEvent(self, event):
if event.button == QtCore.Qt.LeftButton:
self.drawing = False
def sizeHint(self):
return self.image.size()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.label = Label()
self.textedit = QtWidgets.QTextEdit()
widget = QtWidgets.QWidget()
self.setCentralWidget(widget)
lay = QtWidgets.QHBoxLayout(widget)
lay.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
lay.addWidget(self.textedit)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

QRubberBand move on QGraphicsView after resizing

I have the same problem from this topic: QRubberBand move when I resize window, after a few try I realized that solution from this topic doesn't apply on QGraphics View. Why my selection move, arout QgraphicsView when I resize window.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
# from PyQt4 import QtCore, QtWidgets
class ResizableRubberBand(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(ResizableRubberBand, self).__init__(parent)
self.draggable = False
self.mousePressPos = None
self.mouseMovePos = None
self._band = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self)
self._band.setGeometry(550, 550, 550, 550)
self._band.show()
self.show()
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.RightButton:
self.mousePressPos = event.globalPos() # global
self.mouseMovePos = event.globalPos() - self.pos() # local
self.draggable = True
elif event.button() == QtCore.Qt.LeftButton:
self.position = QtCore.QPoint(event.pos())
self.upper_left = self.position
self.lower_right = self.position
self.mode = "drag_lower_right"
self._band.show()
def mouseMoveEvent(self, event):
if self.draggable and event.buttons() & QtCore.Qt.RightButton:
globalPos = event.globalPos()
print(globalPos)
diff = globalPos - self.mouseMovePos
self.move(diff)
self.mouseMovePos = globalPos - self.pos()
elif self._band.isVisible():
# visible selection
if self.mode is "drag_lower_right":
self.lower_right = QtCore.QPoint(event.pos())
# print(str(self.lower_right))
elif self.mode is "drag_upper_left":
self.upper_left = QtCore.QPoint(event.pos())
# print(str(self.upper_left))
# update geometry
self._band.setGeometry(QtCore.QRect(self.upper_left, self.lower_right).normalized())
def mouseReleaseEvent(self, event):
self.draggable = False
my main class:
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.band = ResizableRubberBand()
scene = QtWidgets.QGraphicsScene(self)
photo = QtGui.QPixmap('image.jpg')
scene.addPixmap(photo)
self.band.setScene(scene)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.band)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(800, 100, 600, 500)
window.show()
sys.exit(app.exec_())
before resize:
after resize:
The problem is caused because the coordinate system of the image is not the same as that of the QRubberBand. So the solution is that you both share the same coordinate system and for this we add the QRubberBand to the scene.
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.m_rubberBand = QtWidgets.QRubberBand(
QtWidgets.QRubberBand.Rectangle
)
self.m_rubberBand.setGeometry(QtCore.QRect(-1, -1, 2, 2))
self.m_rubberBand.hide()
item = self.scene().addWidget(self.m_rubberBand)
item.setZValue(1)
self.m_draggable = False
self.m_origin = QtCore.QPoint()
def mousePressEvent(self, event):
self.m_origin = self.mapToScene(event.pos()).toPoint()
self.m_rubberBand.setGeometry(
QtCore.QRect(self.m_origin, QtCore.QSize())
)
self.m_rubberBand.show()
self.m_draggable = True
super(GraphicsView, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.m_draggable:
end_pos = self.mapToScene(event.pos()).toPoint()
self.m_rubberBand.setGeometry(
QtCore.QRect(self.m_origin, end_pos).normalized()
)
self.m_rubberBand.show()
def mouseReleaseEvent(self, event):
end_pos = self.mapToScene(event.pos()).toPoint()
self.m_rubberBand.setGeometry(
QtCore.QRect(self.m_origin, end_pos).normalized()
)
self.m_draggable = False
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
photo = QtGui.QPixmap("image.jpg")
w.scene().addPixmap(photo)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

How to get widgets under a QPaintEvent overlay to register mouse events

I am using an overlay with a QPaintEvent to draw over other widgets. I still need the widgets underneath to register mouse events. When I call raise() with the WA_TransparentForMouseEvents flag, I gain control of the widgets again, but of course, lose the paintevent as it's no longer registering any mouse events. What are my options here?
class Overlay(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Overlay, self).__init__(parent)
self.hotBox = parent
self.resize(self.hotBox.width, self.hotBox.height)
def paintEvent(self, event):
#args: [QEvent]
if any ([self.hotBox.name=="main", self.hotBox.name=="viewport"]):
self.raise_()
self.setWindowFlags(QtCore.Qt.WA_TransparentForMouseEvents)
#Initialize painter
painter = QtGui.QPainter(self)
pen = QtGui.QPen(QtGui.QColor(115, 115, 115), 3, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
painter.setPen(pen)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.setBrush(QtGui.QColor(115, 115, 115))
painter.drawEllipse(self.hotBox.point, 5, 5)
#perform paint
if self.hotBox.mousePosition:
mouseX = self.hotBox.mousePosition.x()
mouseY = self.hotBox.mousePosition.y()
line = QtCore.QLine(mouseX, mouseY, self.hotBox.point.x(), self.hotBox.point.y())
painter.drawLine(line)
painter.drawEllipse(mouseX-5, mouseY-5, 10, 10)
If you want to create an overlay using the events mouseXXXEvent you must use an eventFilter, for this solution I based on this answer of #KubaOber adding more functionalities:
import sys
from PySide2 import QtCore, QtGui, QtWidgets
class Overlay(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Overlay, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
self.start_line, self.end_line = QtCore.QPoint(), QtCore.QPoint()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.fillRect(self.rect(), QtGui.QColor(80, 80, 255, 128))
if not self.start_line.isNull() and not self.end_line.isNull():
painter.drawLine(self.start_line, self.end_line)
def mousePressEvent(self, event):
self.start_line = event.pos()
self.end_line = event.pos()
self.update()
def mouseMoveEvent(self, event):
self.end_line = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.start_line = QtCore.QPoint()
self.end_line = QtCore.QPoint()
class OverlayFactoryFilter(QtCore.QObject):
def __init__(self, parent=None):
super(OverlayFactoryFilter, self).__init__(parent)
self.m_overlay = None
def setWidget(self, w):
w.installEventFilter(self)
if self.m_overlay is None:
self.m_overlay = Overlay()
self.m_overlay.setParent(w)
def eventFilter(self, obj, event):
if not obj.isWidgetType():
return False
if event.type() == QtCore.QEvent.MouseButtonPress:
self.m_overlay.mousePressEvent(event)
elif event.type() == QtCore.QEvent.MouseButtonRelease:
self.m_overlay.mouseReleaseEvent(event)
elif event.type() == QtCore.QEvent.MouseMove:
self.m_overlay.mouseMoveEvent(event)
elif event.type() == QtCore.QEvent.MouseButtonDblClick:
self.m_overlay.mouseDoubleClickEvent(event)
elif event.type() == QtCore.QEvent.Resize:
if self.m_overlay and self.m_overlay.parentWidget() == obj:
self.m_overlay.resize(obj.size())
elif event.type() == QtCore.QEvent.Show:
self.m_overlay.raise_()
return super(OverlayFactoryFilter, self).eventFilter(obj, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
factory = OverlayFactoryFilter()
w = QtWidgets.QWidget()
factory.setWidget(w)
button = QtWidgets.QPushButton("Press me", w)
w.show()
sys.exit(app.exec_())

How to draw a rectangle and adjust its shape by drag and drop in PyQt5

I'm trying to draw a rectangle on GUI created by PyQt5 by drag and drop. I managed to do that, but the rectangle is drawn when the mouse left key is released.
What I want to do is like this link:
When the mouse left button is pressed, start drawing the rectangle.
While dragging, adjust the rectangle shape with the mouse movement.
When the mouse left button is released, determine the rectangle shape.
How can I implement this? Thanks in advance.
Here's my code.
# -*- coding: utf-8 -*-
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtGui import QPainter
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setGeometry(30,30,600,400)
self.pos1 = [0,0]
self.pos2 = [0,0]
self.show()
def paintEvent(self, event):
width = self.pos2[0]-self.pos1[0]
height = self.pos2[1] - self.pos1[1]
qp = QPainter()
qp.begin(self)
qp.drawRect(self.pos1[0], self.pos1[1], width, height)
qp.end()
def mousePressEvent(self, event):
self.pos1[0], self.pos1[1] = event.pos().x(), event.pos().y()
print("clicked")
def mouseReleaseEvent(self, event):
self.pos2[0], self.pos2[1] = event.pos().x(), event.pos().y()
print("released")
self.update()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MyWidget()
window.show()
app.aboutToQuit.connect(app.deleteLater)
sys.exit(app.exec_())
You do not have to use the mouseReleaseEvent function, but the mouseMoveEvent function that is called each time the mouse is moved, and I have modified the code to make it simpler.
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setGeometry(30,30,600,400)
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.show()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
br = QtGui.QBrush(QtGui.QColor(100, 10, 10, 40))
qp.setBrush(br)
qp.drawRect(QtCore.QRect(self.begin, self.end))
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.update()

Categories