I want to create a draggable and resizable frameless window in Pyside6 by rewritting the mouseEvent and resizeEvent.And try to use QSizeGrip to control the shape of window.
Drag and resize, I can implement both functions alone, but there is a problem when they are combined.
when I resize the window after dragging, the position will be wrong. I want to know what's the wrong in this codeļ¼
import sys
from PySide6.QtCore import *
from PySide6.QtWidgets import *
from PySide6.QtGui import *
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.text = QLabel("Hello World",alignment=Qt.AlignCenter)
self.layout =QVBoxLayout(self)
self.layout.addWidget(self.text)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.gripSize = 16
self.grips = []
for i in range(4):
grip = QSizeGrip(self)
grip.resize(self.gripSize, self.gripSize)
self.grips.append(grip)
def resizeEvent(self, event):
QWidget.resizeEvent(self, event)
rect = self.rect()
# top left grip doesn't need to be moved...
# top right
self.grips[1].move(rect.right() - self.gripSize, 0)
# bottom right
self.grips[2].move(
rect.right() - self.gripSize, rect.bottom() - self.gripSize)
# bottom left
self.grips[3].move(0, rect.bottom() - self.gripSize)
def mousePressEvent(self, event):
self.oldPos = event.globalPos()
def mouseMoveEvent(self, event):
delta = QPoint(event.globalPos() - self.oldPos)
self.move(self.x() + delta.x(), self.y() + delta.y())
self.oldPos = event.globalPos()
if __name__ == "__main__":
app =QApplication([])
By default, QSizeGrip interfaces with the OS for the actual resizing as soon as it's activated (by pressing the left mouse button on it).
The result is that, after that, all mouse move events are intercepted by the system until the button is released. Since the button release is also intercepted by the system (to know that the resizing has been completed), QSizeGrip will be able to handle again mouse events only after the button release; since the previous condition was the mouse button press, it will receive a MouseMove event, and, by defaults, those events are ignored by widgets if they don't handle it.
If a mouse event is ignored, it is propagated to its parent(s), which in this case is your MyWidget.
Unfortunately, your assumption is that you only get mouse move events only after a button press, but, due to what explained above, this is not the case: you will not receive a mouse button press (it was handled by the size grip), but only a mouse move (since it's been ignored by the size grip).
Now, there are two cases:
you previously moved the window, so there is an oldPos based on the previous start mouse position, and the window will be moved using the wrong parameters;
you only resized the window since startup, and the program will crash because there was no oldPos attribute;
There are various possible solutions, but the simple one is to create a default oldPos attribute having a None value, set it in the mouse press, check if self.oldPos is not None in the mouse move (and eventually move) and, most importantly, restore self.oldPos = None in the mouse release.
Note that it's usually better to move the window only using a single button (the convention is the left one, but the middle one is not uncommon)
class MyWidget(QWidget):
oldPos = None
# ...
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self.oldPos = event.globalPos()
def mouseMoveEvent(self, event):
if self.oldPos is not None:
delta = event.globalPos() - self.oldPos
self.move(self.pos() + delta)
self.oldPos = event.globalPos()
def mouseReleaseEvent(self, event):
self.oldPos = None
Note: QPoint + QPoint is already a QPoint, and move() accepts a QPoint by default, so there's no need to sum x and y coordinates individually.
Related
I have a mouseReleaseEvent in my QGraphicsView class. By the way, I am using mouseReleaseEvent since if I used the mousePressEvent nothing will happen, no ellipse will be added to the the scene. I don't know how I can find the exact position of the mouse so that the I can use it as the position for my ellipse.Currenlty mo code can addEllipse but the position is not the same in the mouse, same in the picture here:
Update Image here
self._scene = QtWidgets.QGraphicsScene(self)
def mouseReleaseEvent(self, event):
x = event.pos().x()
y = event.pos().y()
obj = self._scene.addEllipse(self._size/2, self._size/2, 10, 10,Qt.red)
obj.setPos(x,y)
self.objects.append(obj)
#self.posprev =pos
I tried using obj.ScenePos() to get the postion but it still does not work.
In PyQt5, I am developing a sensors based GUI where i draw a toggle power off/on button in which i want to add a functionality where i toggle the power button and my desktop gui should be closed on that toggle. like we do in close [X] button of the gui.
here is the toggle.py code and call i on my main.py code
main.py
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# main dialog box
self.turning_Icon = None
self.setWindowTitle("Hatchery System")
self.setStyleSheet("background-color: #2c313c;")
self.setFixedWidth(1400)
self.setFixedHeight(950)
self.setWindowFlags(Qt.FramelessWindowHint)
# Create Container and Layout
self.container = QFrame(self)
self.container.move(100, 50)
self.container.resize(100, 50)
self.container.setStyleSheet("background-color: #2c313c;")
self.container.layout = QVBoxLayout()
# toggle_power_button
self.toggle = PyToggle()
self.toggle.setStyleSheet("background-color: white")
self.toggle.move(50, 50)
self.container.layout.addWidget(self.toggle, Qt.AlignCenter, Qt.AlignCenter)
self.container.setLayout(self.container.layout)
toggle.py code:
import pylab as p
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class PyToggle(QCheckBox):
def __init__(
self,
width=60,
# height=50,
bg_color="#777",
circle_color="#fff",
active_color="#00BCff"
# active_color="red"
):
QCheckBox.__init__(self)
self.setFixedSize(width, 28)
# self.setFixedSize(height, 40)
self.setCursor(Qt.PointingHandCursor)
# colors
self._bg_color = bg_color
self._circle_color = circle_color
self._active_color = active_color
# connect state changed
self.stateChanged.connect(self.debug)
def debug(self):
print(f"status: {self.isChecked()}")
def hitButton(self, pos: QPoint):
return self.contentsRect().contains(pos)
def paintEvent(self, e):
# SET painter
p = QPainter(self)
p.setRenderHint(QPainter.Antialiasing)
p.setPen(Qt.NoPen)
rect = QRect(0, 0, self.width(), self.height())
p.setBrush(QColor(self._bg_color))
p.drawRoundedRect(0, 0, rect.width(), self.height(), self.height() / 2, self.height() / 2)
p.end()
def paintEvent(self, e):
# SET painter
p = QPainter(self)
p.setRenderHint(QPainter.Antialiasing)
# SET as No PEN
p.setPen(Qt.NoPen)
# draw rect
rect = QRect(0, 0, self.width(), self.height())
if not self.isChecked():
# draw BG
p.setBrush(QColor(self._bg_color))
p.drawRoundedRect(0, 0, rect.width(), self.height(), self.height()/2, self.height()/2)
p.setBrush(QColor(self._circle_color))
p.drawEllipse(3, 3, 22, 22)
p.mousePressEvent = self.clickLine
else:
p.setBrush(QColor(self._active_color))
p.drawRoundedRect(0, 0, rect.width(), self.height(), self.height() / 2, self.height() / 2)
p.setBrush(QColor(self._circle_color))
p.drawEllipse(self.width() - 26, 3, 22, 22)
def clickLine(self, mouseEvent):
p.clicked.connect(self.close)
here in if condition i call mousePressEvent but its not working
Output:
on unchecked it off my desktop gui should be close.
Since the requirement is to close the window only when the check box is unchecked again, the solution is to connect to a function that will only close in that case. Note that you should not use the stateChanged signal for dual state check boxes, as it returns a Qt.CheckState enum, which has 3 states. Use the toggled signal instead.
self.toggled.connect(self.checkForClose)
def checkForClose(self, state):
if not state:
self.close()
An important note about your attempt.
First of all, a paint function should never try to do something that is not directly related to drawing. This is important as those functions are called very often, potentially dozens of times each second, and in some cases even every time the mouse is moved above the widget.
Most importantly, your overwritten method just connects the clicked signal, which is wrong for two reasons:
your override doesn't do anything else except for connecting the signal, and since QCheckBox needs to handle mouse events in order to properly change its state (and eventually emit the clicked signal), you're preventing all of that - and the signal will never be emitted because it never gets checked/unchecked;
every time any mouse button will be pressed, the signal would be connected; for the above reason, it will have no result at all, but this is still conceptually wrong, as a connected function will always be called as many times as it has been connected to the signal: you connect it twice? the function will be called twice; assuming that the signal would be emitted (which could still happen using keyboard), the related function will be called as many times as the user has attempted to click it.
Another important aspect to remember is that PyQt uses reference caching for functions; after the first time a virtual function is called with the default implementation, overwriting the instance attribute will have no effect.
Supposing you wanted to do the opposite (connect the signal when the state is True), it wouldn't have worked because at that time the default mousePressEvent handler would have been already called, and the overwriting would have had no effect at all.
I'm working with pyqt5 and qt designer in python 3.7.x and my draggable QLabels don't seems to work. I'm trying to set de Labels as draggable and drop them into another Label, but my code doesn't work, I made the window in qt designer and also defined the Lebels there, but when I try to "transform" the QLabels into a draggable objects nothing happens. Here is some of my code and I'll explain:
class Window(window_name, base_class):
def __init__(self):
super().__init__()
self.setupUi(self)
self.penguin_list = (
self.cyan,
self.red,
self.yellow,
self.purple,
self.green
) # This tuple is made out of the QLabel created on qt designer that i want to drag
def mousePressEvent(self, event):
# Here I tried to set the QLabel that I want to drag (the one the mouse pressed)
if event.button() == Qt.LeftButton:
for penguin in self.penguin_list:
if penguin.underMouse():
self.penguin = penguin
self.drag_start_position = event.pos()
def mouseMoveEvent(self, event):
# Here is when I tried to convert the QLavel into draggable
# Also the QLabel appears in the top left corner (sometimes) with a white background
# And also the drop part doesn't work and I can not drop the QLabel
if not (event.buttons() & Qt.LeftButton):
return
if (event.pos() - self.drag_start_position).manhattanLength() < QApplication.startDragDistance():
return
drag = QDrag(self.penguin)
mimedata = QMimeData()
mimedata.setImageData(self.penguin.pixmap())
drag.setMimeData(mimedata)
pixmap = QPixmap(self.penguin.size())
painter = QPainter(pixmap)
painter.drawPixmap(self.penguin.rect(), self.penguin.grab())
painter.end()
drag.setPixmap(pixmap)
drag.setHotSpot(event.pos())
drag.exec_(Qt.CopyAction | Qt.MoveAction)
Overall I wanna make a copy of the QLabel when i click on it and drop it (pasted) in other QLabel that is in other QFrame.
I am trying to draw over image using QPainter. It works good when using solid color. When using semi transparent color, dots were appearing.
Also when drawing multiple lines in one place, the color gets multiplied and produces a darker color.
import sys
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QPainter, QPen, QColor
class Menu(QMainWindow):
def __init__(self):
super().__init__()
self.drawing = False
self.lastPoint = QPoint()
self.image = QPixmap(r"C:\Users\www\Desktop\image.jpg")
self.setGeometry(100, 100, 500, 300)
self.resize(self.image.width(), self.image.height())
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()
def mouseMoveEvent(self, event):
if event.buttons() and Qt.LeftButton and self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(QColor(121,252,50,50), 20, Qt.SolidLine))
painter.drawLine(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_())
I need to keep the color as the original color(instead of getting darker each time) when drawing several times over the same place.
Mouse movement are "discrete", which means that whenever you move your mouse you won't get continuous pixel coordinates: if you move your mouse fast enough from (0, 0) to (20, 20), you'll probably get only two or three mouseMoveEvents in the middle at most, resulting in single segments for each mouse event.
The "dots" you see are actually areas where the different lines you draw collide, expecially since the mouse movement are not continuous. If you think of it as painting with watercolors, it's like if you draw a small line at each mouse movement, then wait until it's dried, then start to paint another line from the previous point.
As soon as you draw unique lines at each mouseMoveEvent, the edges of those segments are superimposed, resulting in those "less transparent dots" (since you're using a non opaque color), which are the points where the segments collide, and, because painting is usually "additive" you get two or more areas where the superimposed color results in a more opaque one: imagine it as watching through two pairs of sunglasses that are not aligned.
QPainterPath, instead, can draw continuous lines without that "artifact", as long as they are part of the same painter path (no matter its subpath, including subpath polygons, ellipses, arcs, etc.). Then, whenever you tell the QPainter to draw a new element, it will be superimposed to the previous ones.
To better clarify, in this image on the left I'm drawing two distinct lines with a common vertex, using your color, which would be the case of a mousePressEvent (start drawing), a fast movement to the right (draw the first line) and another one to the bottom (draw another line). On the right there are the same "lines", but using a unique QPainterPath.
In this example code I temporarily create a painter path, which stores the current "drawing path" until the mouse is released, after which the path is actually applied to the QPixmap.
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPixmap, QPainter, QPen, QColor, QPainterPath
class Menu(QWidget):
def __init__(self):
super().__init__()
self.drawingPath = None
self.image = QPixmap(r"testimage.jpg")
self.resize(self.image.width(), self.image.height())
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.image)
if self.drawingPath:
painter.setPen(QPen(QColor(121,252,50,50), 20, Qt.SolidLine))
painter.drawPath(self.drawingPath)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
# start a new QPainterPath and *move* to the current point
self.drawingPath = QPainterPath()
self.drawingPath.moveTo(event.pos())
def mouseMoveEvent(self, event):
if event.buttons() and Qt.LeftButton and self.drawingPath:
# add a line to the painter path, without "removing" the pen
self.drawingPath.lineTo(event.pos())
self.update()
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton and self.drawingPath:
# draw the painter path to the pixmap
painter = QPainter(self.image)
painter.setPen(QPen(QColor(121,252,50,50), 20, Qt.SolidLine))
painter.drawPath(self.drawingPath)
self.drawingPath = None
self.update()
if __name__ == '__main__':
app = QApplication(sys.argv)
mainMenu = Menu()
sys.exit(app.exec_())
There is only one problem with this: drawing over a currently drawing path won't result in a more opaque color, meaning that, as long as the mouse button is pressed, no matter how many times you "paint" over the same point, the color will always be the same. To get the "more opaque color" effect, you'll need to paint over the intersection(s), starting a new path each time.
PS: I used a QWidget, as in some cases a QMainWindow can grab mouse movements starting from a click on a non interactive area (like in this case) and use it to move the interface.
I am trying to create a scenario where I need to draw line from the mousePressEvent position till the latest mouse moveposition which means i need to call paintEvent from mousePressEvent ,Is it possible ?
So scenario is this :
1) Used paintEvent to draw a 2 circles with black colour
2) Mouse press event waits for a event and press happens , I want to change the colour of the circle to green , is it possible ?
import sys, random
from PyQt4 import QtGui, QtCore
class P(QtGui.QWidget):
def __init__(self):
super(P, self).__init__()
self.initUI()
def initUI(self):
q=self.frameGeometry()
cp=QtGui.QDesktopWidget().availableGeometry().center()
q.moveCenter(cp)
self.setFixedSize(300,300)
self.setWindowTitle('Points')
self.show()
def mousePressEvent(self, QMouseEvent):
cursor =QtGui.QCursor(self)
position = QMouseEvent.pos()
xpos = QMouseEvent.x()
ypos = QMouseEvent.y()
#Trial ??????
q = QtGui.QPainter()
q.drawLine(30,30,90,90)
print QMouseEvent.pos()
def mouseReleaseEvent(self, QMouseEvent):
cursor =QtGui.QCursor()
print cursor.pos()
def paintEvent(self,e):
qp = QtGui.QPainter()
qp.begin(self)
E1 = qp.drawEllipse(30,30,20,20)
E2 = qp.drawEllipse(30,130,20,20)
def main():
app = QtGui.QApplication(sys.argv)
ex = P()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
In Simple words I need to know can we call one Event from another , i.e. Paint Event from Mouse press event ?
It is a much better idea to do all your painting in the paintEvent handler.
You should use your mouse event handlers to handle the collection of data (starting points, lengths, etc) and then do the actual repainting in the paintEvent.
Once you've collected the new data in the mouse event handlers, you can tell the QWidget that it needs to repaint by calling update function. This will schedule a paint event that will execute when the program returns to the event loop.