PySide: Movable labels snap back to original position when released. Trying to make them move freely

岁酱吖の 提交于 2019-12-24 19:22:07

问题


The following code implements 5 movable labels. When I try to change the color of the labels while they are being moved, the labels snap back to the original position when the mouse button is released. When you comment out the part where setStyleSheet is used, it works and the labels are able to move and be released freely.

import sys
from PySide import QtGui
from PySide import QtCore
from PySide.QtGui import *
from PySide.QtCore import *
from Drag import Ui_Dialog

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
    self.mainMenuWidget = MainStack(self)   
    self.setCentralWidget(self.mainMenuWidget)
        self.show()


class MainStack(QWidget):
    def __init__(self,parent=None):
        super(MainStack,self).__init__(parent)
        self.initUI()

    def initUI(self):
        layout = QVBoxLayout(self)
        self.stack = QStackedWidget(parent=self)
        self.dragPage = DragClass()
        #Add Pages to Stack
        self.stack.addWidget(self.dragPage)
        #Add Stack to Layout    
        self.stack.setCurrentWidget(self.dragPage)
        layout.addWidget(self.stack)

class DragClass(QDialog):
    def __init__(self, parent=None):
        super(DragClass, self).__init__(parent)
        self.LabelGrid = Ui_Dialog()
        self.LabelGrid.setupUi(self)
        self.configureLabels() 

    def configureLabels(self):
        labels = (self.LabelGrid.verticalLayout.itemAt(i) for i in range(self.LabelGrid.verticalLayout.count()))
        for label in labels:
            label = label.widget()
            if (isinstance(label,DragButton)) :
                label.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
                label.setStyleSheet("""
                    background-color: lightblue;
                    border-width: 2px;
                    border-style: solid;
                    border-color: black;
                    margin: 2px;
                """)

#########DragButton Class#############
class DragButton(QLabel):

    def mousePressEvent(self, event):
        self.__mousePressPos = None
        self.__mouseMovePos = None
        if event.button() == QtCore.Qt.LeftButton:
            self.__mousePressPos = event.globalPos()
            self.__mouseMovePos = event.globalPos()
        self.start_move = 0
        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

    #If you Uncomment these blocks, the labels are no longer able to move freely. They snap back to their original position when released

            #if not self.start_move: 
                #self.setStyleSheet("background-color: red;")

            #if not self.start_move:
                #self.start_move = 1


        super(DragButton, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if self.__mousePressPos is not None:


        #if self.start_move:
            #self.setStyleSheet("background-color: lightblue;")

            moved = event.globalPos() - self.__mousePressPos

            if moved.manhattanLength() > 3:
                event.ignore()
                return

        super(DragButton, self).mouseReleaseEvent(event)
##############################################


if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWin = MainWindow()
    ret = app.exec_()
    sys.exit( ret )

UI Class for the draggable labels:

from PySide import QtCore, QtGui

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 300)
        self.gridLayout = QtGui.QGridLayout(Dialog)
        self.gridLayout.setObjectName("gridLayout")
        self.verticalLayout = QtGui.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.option1 = DragButton(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.option1.setFont(font)
        self.option1.setFrameShape(QtGui.QFrame.StyledPanel)
        self.option1.setFrameShadow(QtGui.QFrame.Raised)
        self.option1.setAlignment(QtCore.Qt.AlignCenter)
        self.option1.setObjectName("option1")
        self.verticalLayout.addWidget(self.option1)
        self.option2 = DragButton(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.option2.setFont(font)
        self.option2.setFrameShape(QtGui.QFrame.StyledPanel)
        self.option2.setFrameShadow(QtGui.QFrame.Raised)
        self.option2.setAlignment(QtCore.Qt.AlignCenter)
        self.option2.setObjectName("option2")
        self.verticalLayout.addWidget(self.option2)
        self.Option3 = DragButton(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.Option3.setFont(font)
        self.Option3.setFrameShape(QtGui.QFrame.StyledPanel)
        self.Option3.setFrameShadow(QtGui.QFrame.Raised)
        self.Option3.setAlignment(QtCore.Qt.AlignCenter)
        self.Option3.setObjectName("Option3")
        self.verticalLayout.addWidget(self.Option3)
        self.Option4 = DragButton(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.Option4.setFont(font)
        self.Option4.setFrameShape(QtGui.QFrame.StyledPanel)
        self.Option4.setFrameShadow(QtGui.QFrame.Raised)
        self.Option4.setLineWidth(1)
        self.Option4.setMidLineWidth(0)
        self.Option4.setAlignment(QtCore.Qt.AlignCenter)
        self.Option4.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
        self.Option4.setObjectName("Option4")
        self.verticalLayout.addWidget(self.Option4)
        self.Option5 = DragButton(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.Option5.setFont(font)
        self.Option5.setFrameShape(QtGui.QFrame.StyledPanel)
        self.Option5.setFrameShadow(QtGui.QFrame.Raised)
        self.Option5.setLineWidth(1)
        self.Option5.setMidLineWidth(0)
        self.Option5.setAlignment(QtCore.Qt.AlignCenter)
        self.Option5.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
        self.Option5.setObjectName("Option5")
        self.verticalLayout.addWidget(self.Option5)
        self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.option1.setText(QtGui.QApplication.translate("Dialog", "Option 1", None, QtGui.QApplication.UnicodeUTF8))
        self.option2.setText(QtGui.QApplication.translate("Dialog", "Option 2", None, QtGui.QApplication.UnicodeUTF8))
        self.Option3.setText(QtGui.QApplication.translate("Dialog", "Option 3", None, QtGui.QApplication.UnicodeUTF8))
        self.Option4.setText(QtGui.QApplication.translate("Dialog", "Option 4", None, QtGui.QApplication.UnicodeUTF8))
        self.Option5.setText(QtGui.QApplication.translate("Dialog", "Option 5", None, QtGui.QApplication.UnicodeUTF8))

from app import DragButton

回答1:


Whenever a widget is added to a layout, its size and position is decided by that layout (according to the available size and other widgets that are part of the layout).

While it is possible to change the geometry of a widget while it's part of a layout, any change to its contents will automatically be notified to the layout, that will then update itself accordingly.
Those changes include different size, different constraints (minimum/maximum size), the size policy (the ability to expand or shrink).

When a stylesheet is set to a widget, its content are immediately invalidated and computed again (even if the stylesheet is the same); the reason for which you can't move the widget is that as soon as the stylesheet is applied, the layout that contains it will force it again to its original position.

You can see what happens if you keep the commented lines, move a widget and then resize the window: the moved widget will be repositioned again as the layout requires.

If you want to be able to move a widget freely you need to create the movable widgets as children of the widget that will contain them, but not in a layout.
This obviously becomes a problem if you want to "lay out" them similarly to how a real QLayout would, but within the resizeEvent of the widget that contains them.

In this specific case:

class DragClass(QDialog):
    def __init__(self, parent=None):
        super(DragClass, self).__init__(parent)
        self.LabelGrid = Ui_Dialog()
        self.LabelGrid.setupUi(self)
        self.configureLabels() 

    def configureLabels(self):
        # Note that since the labels are not part of a layout anymore, now, I
        # had to use another way to "find them". Normally one would go with
        # findChildren, but that's not our case because of the way the DragButton
        # class is imported (it actually is snap.DragButton)
        self.labels = [child for child in self.children() if child.__class__.__name__ == 'DragButton']
        for label in self.labels:
            if (isinstance(label,DragButton)) :
                label.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
                label.setStyleSheet("""
                    background-color: lightblue;
                    border-width: 2px;
                    border-style: solid;
                    border-color: black;
                    margin: 2px;
                """)

    def resizeEvent(self, event):
        margin = 5
        spacing = 4

        innerRect = self.rect().adjusted(margin, margin, -margin, -margin)

        count = len(self.labels)
        availableHeight = innerRect.height() - spacing * (count - 1)
        labelSize = availableHeight / count

        top = innerRect.y()
        left = innerRect.left()
        width = innerRect.width()
        for label in self.labels:
            rect = QtCore.QRect(left, top, width, labelSize)
            label.setGeometry(rect)
            top = rect.bottom() + spacing

In this way, the labels are aligned to a "virtual" layout when shown the first time, but now can be freely moved around. Obviously, with this simple implementation, as soon as the window is resized, they will be repositioned again, so it's up to you to decide what to do with labels that have been manually moved.

As a side note, you should not use a QDialog class for what you need. Dialogs are used as windows that appear over other existing windows. For your needs, a simple QWidget will suffice.



来源:https://stackoverflow.com/questions/59221822/pyside-movable-labels-snap-back-to-original-position-when-released-trying-to-m

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!