When moving a widget, it will only move over a certain area and not across the entire monitor
I have a widget transfer method but it doesn't work correctly
def mousePressEvent(self, event):
self.oldPos = event.globalPosition()
def mouseMoveEvent(self, event):
delta = event.globalPosition() - self.oldPos
self.move(int(self.x() + delta.x()), int(self.y() + delta.y()))
self.oldPos = event.globalPosition()
Related
I have a scene with QGraphicsPixmapItem, and show a QGraphicScene object like a rectangle.
def setupUI(self):
self.pixmap01 = QPixmap.fromImage(qImg)
self.mainpic = QGraphicsPixmapItem(self.pixmap01)
self.scene1.addItem(self.mainpic)
self.graphicsViewScene1.setScene(self.scene1)
self.scene1.update()
def DrawRectangle(self):
scene = RectangleScene()
scene.addItem(self.mainpic)
self.graphicsViewScene1.setScene(scene)
Now i a draw rectangle and top of a scene(this part of code is fine) but after right click on rect and choose replace pic item(option menu) i want to replace this selected area of QPixmap with another QPixmap(for this action i use Qpainter but nothing happens)
class RectangleScene(QGraphicsScene):
def __init__(self, *args, **kwargs):
super(RectangleScene, self).__init__(*args, **kwargs)
def mousePressEvent(self, event):
self.clean_scene()
self.start_point = event.scenePos()
self.end_point =self.start_point
self.graphics_line = QGraphicsRectItem(QRectF(self.start_point, self.end_point))
self.update_path()
def mouseMoveEvent(self, event):
self.end_point = event.scenePos()
self.update_path()
def mouseReleaseEvent(self, event):
self.end_point = event.scenePos()
self.update_path()
def update_path(self):
self.graphics_line.setRect(QRectF(self.start_point, self.end_point))
self.addItem(self.graphics_line)
def contextMenuEvent(self, event):
menu = QtWidgets.QMenu()
f1 = menu.addAction("repalce pic")
if action == f1:
tmp_rect = QRectF(self.start_point, self.end_point)
new_pix_map = QPixmap.fromImage(qImg)
painter = QPainter(self.imageProcessing.pixmap01)
painter.drawPixmap(tmp_rect,new_pix_map ,tmp_rect)
in another part of code after select this area, get a ndarray of this area and add some filters then i want to add top of this rect
I am just curious if I can make a nested QRubberband. (I or someone might find a use to it). I managed to edit the code from this answer to make a nested QRubberband. It is all fine and working until I move the QRubberband inside its parent QRubberband. I was very confused as it moves wildly when I'm dragging it.
This is the sample code:
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class ResizableRubberBand(QRubberBand):
moving = False
def __init__(self, parent=None):
super(ResizableRubberBand, self).__init__(QRubberBand.Rectangle, parent)
self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
self.draggable = True
self.dragging = False
self.is_dragging = False
self.dragging_threshold = 5
self.mousePressPos = None
self.borderRadius = 5
self.setWindowFlags(Qt.SubWindow)
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(
QSizeGrip(self), 0,
Qt.AlignLeft | Qt.AlignTop)
layout.addWidget(
QSizeGrip(self), 0,
Qt.AlignRight | Qt.AlignBottom)
self.show()
def resizeEvent(self, event):
self.clearMask()
def paintEvent(self, event):
super().paintEvent(event)
qp = QPainter(self)
qp.setRenderHint(QPainter.Antialiasing)
qp.translate(.5, .5)
qp.drawRoundedRect(self.rect().adjusted(0, 0, -1, -1),
self.borderRadius, self.borderRadius)
def mousePressEvent(self, event):
if self.draggable and event.button() == Qt.RightButton:
self.mousePressPos = event.pos()
if event.button() == Qt.LeftButton:
self.first_mouse_location = (event.x(), event.y())
self.band = ResizableRubberBand(self)
self.band.setGeometry(event.x(), event.y(), 0, 0)
super(ResizableRubberBand, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.draggable and event.buttons() & Qt.RightButton:
diff = event.pos() - self.mousePressPos
if not self.dragging:
if diff.manhattanLength() > self.dragging_threshold:
self.dragging = True
if self.dragging:
geo = self.geometry()
parentRect = self.parent().rect()
geo.translate(diff)
if not parentRect.contains(geo):
if geo.right() > parentRect.right():
geo.moveRight(parentRect.right())
elif geo.x() < parentRect.x():
geo.moveLeft(parentRect.x())
if geo.bottom() > parentRect.bottom():
geo.moveBottom(parentRect.bottom())
elif geo.y() < parentRect.y():
geo.moveTop(parentRect.y())
self.move(geo.topLeft())
if event.buttons() & Qt.LeftButton:
first_mouse_location_x = self.first_mouse_location[0]
first_mouse_location_y = self.first_mouse_location[1]
new_x, new_y = event.x(), event.y()
difference_x = new_x - first_mouse_location_x
difference_y = new_y - first_mouse_location_y
self.band.resize(difference_x, difference_y)
super(ResizableRubberBand, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self.mousePressPos is not None:
if event.button() == Qt.RightButton and self.dragging:
event.ignore()
self.dragging = False
self.mousePressPos = None
super(ResizableRubberBand, self).mouseReleaseEvent(event)
class mQLabel(QLabel):
def __init__(self, parent=None):
QLabel.__init__(self, parent)
self.setContentsMargins(0,0,0,0)
self.setAlignment(Qt.AlignTop | Qt.AlignLeft)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.first_mouse_location = (event.x(), event.y())
self.band = ResizableRubberBand(self)
self.band.setGeometry(event.x(), event.y(), 0, 0)
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton:
first_mouse_location_x = self.first_mouse_location[0]
first_mouse_location_y = self.first_mouse_location[1]
new_x, new_y = event.x(), event.y()
difference_x = new_x - first_mouse_location_x
difference_y = new_y - first_mouse_location_y
self.band.resize(difference_x, difference_y)
class App(QWidget):
def __init__(self):
super().__init__()
## Set main window attributes
self.setFixedSize(1000,600)
# Add Label
self.label = mQLabel()
self.label.setStyleSheet("border: 1px solid black;")
self.label_layout = QHBoxLayout(self)
self.label_layout.addWidget(self.label)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I'm trying to figure it out for 2 hours but I can't really seem to figure out what causes the unnecessary movement. My best guess is it is coming from the mouseMoveEvent but I'm not quite sure if it is from the parent QRubberband or from the QRubberband inside. I hope someone can figure out what is happening here.
The problem is the call to the base implementation of mouse events, which by default gets propagated to the parent for widgets that do not directly implement them, including QRubberBand, which normally doesn't intercept mouse events at all (which we restored by disabling the relative window attribute).
Since the parent itself is a rubber band, it will be moved itself too, making the movement recursive for the child, since it receives a mouse move exactly due to the fact that its been moved: remember that if a widget is moved and the mouse doesn't directly follow the same movement, it will potentially receive a mouse move event relative to its new position.
You can either return before calling it when you handle it, or not call it at all, depending on your needs.
The important thing is that it's consistent (especially for press and move), otherwise a widget could receive a mouse move without receiving the mouse press, which will crash as the variables have not been set yet.
Be aware that if you're in the process of making a more advanced editor for clipping/selections, drawing, etc, you should really consider using the Graphics View Framework: while much more complex and with a more steep learning curve, you'll soon find out that continuing development on basic QWidgets becomes gradually much more convoluted and difficult, to a point where it is really hard to fix things, especially if you're going to deal with image scaling or even basic scroll and zoom.
QWidget and QLabel implementations are not intended for image management, not even simple editing, and custom placed/painted/nested widgets are often difficult to deal with. Consider that doing a similar selection tool would have been much more easy in a graphics scene: for instance, the moving implementation would be almost completely unnecessary, as it's enough to set a simple flag to make an item movable.
I am trying to do drag and drop of rows inside a QTableWidget.
The drag and drop operation itself is working great, but when I click and release the mouse button rapidly on a row , the drop event is never called. Even with the mouse released, the cursor is still in drag mode until I click again.
When I click an release slowly on a row, to select it, the events are:
- mousePressEvent
- dragEnterEvent
- dropEvent
When I click and release rapidly, the events are:
- mousePressEvent
- mouseReleaseEvent
- dragEnterEvent
and dropEvent is only called if I click again.
This is probably because the dragEnterEvent (that should never be called, I think) 'hides' the mouseReleaseEvent to the drag operation.
Is there a way to force the end of the drag operation ? or better, to prevent the drag operation to call dragEnterEvent ?
Here is my code:
class DragDropTableWidget(QTableWidget):
moveRow = pyqtSignal(int, int)
def __init__(self, parent):
super(QTableWidget, self).__init__(parent)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.viewport().setAcceptDrops(True)
self.setDragDropOverwriteMode(False)
self.setDropIndicatorShown(True)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setDragDropMode(QAbstractItemView.InternalMove)
self.released = False
def mousePressEvent(self, event):
print('Mouse Press')
self.released = False
index = self.indexAt(event.pos())
if not index.isValid():
return
row = index.row()
self.selectRow(row)
mime_data = QMimeData()
mime_data.setData("index", str(row))
drag = QDrag(self)
drag.setMimeData(mime_data)
drag.start(Qt.MoveAction)
def dragEnterEvent(self, e):
print('Drag Enter')
if self.released:
# todo: cancel drag and drop
pass
else:
e.accept()
def dragMoveEvent(self, e):
e.accept()
def mouseReleaseEvent(self, e):
print('Mouse release')
self.released = True
def dropEvent(self, event):
print('Drop event')
if event.source() == self:
index = self.indexAt(event.pos())
if not index.isValid():
event.accept()
return
start_index = int(event.mimeData().data("index"))
if start_index != index.row():
print ("dropEvent called from row {} on row {}".format(start_index, index.row()))
self.moveRow.emit(start_index, index.row())
event.accept()
Thanks a lot
Thank you musicamante for showing me the right direction.
This is a working implementation for a QTableWidget with drag and drop support of table rows:
class DragDropTableWidget(QTableWidget):
# signal sent on drag and drop end operation
moveRow = pyqtSignal(int, int)
def __init__(self, parent):
super(QTableWidget, self).__init__(parent)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.viewport().setAcceptDrops(True)
self.setDragDropOverwriteMode(False)
self.setDropIndicatorShown(True)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setDragDropMode(QAbstractItemView.InternalMove)
def mouseMoveEvent(self, event):
if event.type() == QEvent.MouseMove and event.buttons() & Qt.LeftButton:
index = self.indexAt(event.pos())
if not index.isValid():
return
mime_data = QMimeData()
mime_data.setData("index", str(index.row()))
drag = QDrag(self)
drag.setMimeData(mime_data)
drag.start(Qt.MoveAction)
def dragEnterEvent(self, e):
e.accept()
def dragMoveEvent(self, e):
e.accept()
def dropEvent(self, event):
if event.source() == self:
index = self.indexAt(event.pos())
if not index.isValid():
event.accept()
return
start_index = int(event.mimeData().data("index"))
if start_index != index.row():
print ("dropEvent called from row {} on row {}".format(start_index, index.row()))
self.moveRow.emit(start_index, index.row())
event.accept()
Im trying to recreate a little version of trello in tkinter. Right now im stuck I have a problem when I want to delete frames in a different order. For example: I click on the button and a new frame is generated if I delete that everything works. If I create 3 frames I have to remove them in the same order as I have created them. So I think my problems lies in the pop function but I dont know how to access them manually. When i change the pop function to (1) then I have to delete the second creation first instead of the first.
Here is the code:
from tkinter import *
class Window:
def __init__(self, width, height):
self.root = Tk()
self.width = width
self.height = height
self.root.geometry(width + "x" + height)
class Frames:
def __init__(self):
self.l = Frame(window.root, bg="red", height=300, width=300, relief="sunken")
self.l.place(relwidth=0.3, relheight=0.3)
self.deleteB = Button(self.l, text="X", command=self.delete_frame, bg="blue")
self.deleteB.place(rely=0, relx=0.92)
self.addB = Button(self.l, text="Add", command=self.add_note, bg="blue")
self.addB.place(rely=0, relx=0.65)
def delete_frame(self):
self.l.pack()
self.l.pack_forget()
self.l.destroy()
frames.pop()
def add_note(self):
self.note_Label = Label(self.l, text="Clean the room")
self.note_Label.pack(padx=20, pady=10)
self.delete_Note = Button(self.note_Label, text="X", command=self.del_Note)
self.delete_Note.pack(padx=5, pady=5)
def del_Note(self):
self.note_Label.pack_forget()
self.note_Label.destroy()
class Note:
def __init__(self):
pass
class DragNDrop:
def __init__(self):
pass
def make_draggable(self, widget):
widget.bind("<Button-1>", self.on_drag_start)
widget.bind("<B1-Motion>", self.on_drag_motion)
def on_drag_start(self, event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(self, event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.place(x=x, y=y)
class Buttons:
def __init__(self):
self.button = Button(window.root, width=20, height=20, bg="blue", command=self.add_frames)
self.button.pack()
def add_frames(self):
frames.append(Frames())
print(frames)
window = Window("800", "600")
frames = []
drag = DragNDrop()
button = Buttons()
while True:
for i in frames:
drag.make_draggable(i.l)
window.root.update()
If someone has an Idea or workaround that would be nice to know.
Also I have another Idea instead of destroying them I could just hide them but in the end that makes the programm really slow at some point.
Here is the error: _tkinter.TclError: bad window path name ".!frame2"
Your code needs to remove the frame from the list. Instead, you're calling pop which always removes the last item. That causes you to lose the reference to the last window, and one of the references in frames now points to a window that has been deleted (which is the root cause of the error)
Instead, call remove:
def delete_frame(self):
self.l.destroy()
frames.remove(self)
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.view = View(self)
self.button = QtGui.QPushButton('Clear View', self)
self.button.clicked.connect(self.handleClearView)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.button)
def handleClearView(self):
self.view.scene().clear()
class DragButton(QtGui.QPushButton):
def mousePressEvent(self, event):
self.__mousePressPos = None
self.__mouseMovePos = None
if event.button() == QtCore.Qt.LeftButton:
self.__mousePressPos = event.globalPos()
self.__mouseMovePos = event.globalPos()
#super(DragButton, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
# adjust offset from clicked point to origin of widget
currPos = self.mapToGlobal(self.pos())
globalPos = event.globalPos()
diff = globalPos - self.__mouseMovePos
newPos = self.mapFromGlobal(currPos + diff)
self.move(newPos)
self.__mouseMovePos = globalPos
#super(DragButton, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self.__mousePressPos is not None:
moved = event.globalPos() - self.__mousePressPos
if moved.manhattanLength() > 3:
event.ignore()
return
#super(DragButton, self).mouseReleaseEvent(event)
class View(QtGui.QGraphicsView):
def __init__(self, parent):
QtGui.QGraphicsView.__init__(self, parent)
self.setScene(QtGui.QGraphicsScene(self))
self.setSceneRect(QtCore.QRectF(self.viewport().rect()))
btn1=DragButton('Test1', self)
btn2=DragButton('Test2', self)
def mousePressEvent(self, event):
self._start = event.pos()
def mouseReleaseEvent(self, event):
start = QtCore.QPointF(self.mapToScene(self._start))
end = QtCore.QPointF(self.mapToScene(event.pos()))
self.scene().addItem(
QtGui.QGraphicsLineItem(QtCore.QLineF(start, end)))
for point in (start, end):
text = self.scene().addSimpleText(
'(%d, %d)' % (point.x(), point.y()))
text.setBrush(QtCore.Qt.red)
text.setPos(point)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Here is my code. There are two movable buttons on the QGraphicsView and I can draw line on the QGraphicsView with mouse dragging. But what I want to do is to draw line between two buttons. For detail, If I right click the btn1(Test1) and then right click the btn2(Test2) , the line would be created between two buttons. I'm struggling this problem for a month. Plz Help!
I am assuming that the line you need to draw between the buttons must also be movable. If not it is just simple you can just use :
lines = QtGui.QPainter()
lines.setPen(self)
lines.drawLine(x1,y1,x2,y2)
So, if the line needs to be movable along with the buttons then first you create a mini widget consisting of Two Buttons and the Line, so you can move the whole widget. This might help!, in that case.