Issue
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
Solution
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.
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.