PySide:可移动标签在释放时会弹回原始位置。试图让他们自由行动
PySide: Movable labels snap back to original position when released. Trying to make them move freely
以下代码实现了5个可移动标签。当我尝试在移动标签时更改标签的颜色时,标签会在松开鼠标按钮时弹回到原来的位置。当您注释掉使用 setStyleSheet 的部分时,它起作用并且标签能够自由移动和释放。
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 可拖动标签:
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
无论何时将小部件添加到布局中,其大小和位置都由该布局决定(根据可用大小和作为布局一部分的其他小部件)。
虽然 可以在布局中更改小部件的几何形状,但对其 内容 的任何更改都会自动通知布局,然后相应地更新自身。
这些更改包括不同的大小、不同的约束(minimum/maximum 大小)、大小策略(扩展或收缩的能力)。
当一个样式表被设置为一个小部件时,它的内容会立即失效并重新计算(即使样式表是相同的);您不能移动小部件的原因是,一旦应用样式表,包含它的布局将强制它再次回到原来的位置。
您可以看到如果保留注释行、移动小部件然后调整大小会发生什么window:移动的小部件将根据布局需要重新定位。
如果您希望能够自由移动小部件,您需要将可移动小部件创建为将包含它们的小部件的子项,但不是在布局中。
如果您想像真正的 QLayout 一样 "lay out" 它们,但在包含它们的小部件的 resizeEvent
内,这显然会成为一个问题。
在此特定情况下:
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
这样,标签在第一次显示时与 "virtual" 布局对齐,但现在可以自由移动。显然,通过这个简单的实现,一旦调整 window 的大小,它们将再次重新定位,因此由您决定如何处理手动移动的标签。
附带说明一下,您应该不使用 QDialog class 来满足您的需要。对话框用作 windows,出现在 other 现有 windows 之上。对于您的需求,一个简单的 QWidget 就足够了。
以下代码实现了5个可移动标签。当我尝试在移动标签时更改标签的颜色时,标签会在松开鼠标按钮时弹回到原来的位置。当您注释掉使用 setStyleSheet 的部分时,它起作用并且标签能够自由移动和释放。
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 可拖动标签:
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
无论何时将小部件添加到布局中,其大小和位置都由该布局决定(根据可用大小和作为布局一部分的其他小部件)。
虽然 可以在布局中更改小部件的几何形状,但对其 内容 的任何更改都会自动通知布局,然后相应地更新自身。
这些更改包括不同的大小、不同的约束(minimum/maximum 大小)、大小策略(扩展或收缩的能力)。
当一个样式表被设置为一个小部件时,它的内容会立即失效并重新计算(即使样式表是相同的);您不能移动小部件的原因是,一旦应用样式表,包含它的布局将强制它再次回到原来的位置。
您可以看到如果保留注释行、移动小部件然后调整大小会发生什么window:移动的小部件将根据布局需要重新定位。
如果您希望能够自由移动小部件,您需要将可移动小部件创建为将包含它们的小部件的子项,但不是在布局中。
如果您想像真正的 QLayout 一样 "lay out" 它们,但在包含它们的小部件的 resizeEvent
内,这显然会成为一个问题。
在此特定情况下:
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
这样,标签在第一次显示时与 "virtual" 布局对齐,但现在可以自由移动。显然,通过这个简单的实现,一旦调整 window 的大小,它们将再次重新定位,因此由您决定如何处理手动移动的标签。
附带说明一下,您应该不使用 QDialog class 来满足您的需要。对话框用作 windows,出现在 other 现有 windows 之上。对于您的需求,一个简单的 QWidget 就足够了。