PySide: How to snap a button into a place when I drag it?

微笑、不失礼 提交于 2019-12-13 03:09:43

问题


I have an application where I am able to drag buttons around freely in a window. I want to have a section of the window with slots in which I can drop the button into. If I drop it in the vicinity of one slot, I want the button to lock in place in that slot. I only want to move the buttons in the Y direction.

How would I go about implementing the slots? I am picturing something simple like a bookshelf.

Edit: I've added code that implements 5 movable labels. What I am trying to do is to be able to reorder the labels by dragging and dropping. If I move a label on top of another label, I want the two labels to swap places.

I have attempted to do this, but the problem I have run into is that when I move the widget back to its original position after I have moved it a little, it does not go to its original position. It goes above the original position, even though the coordinate I am moving it to is the same.

Here is some code I have of 5 movable labels. I have stored the top left and bottom left of each label in a class variable in the form of a dictionary.:

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

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(QtGui.QWidget):
    def __init__(self, parent=None):
        super(DragClass, self).__init__(parent)

        self.setStyleSheet("""
            background-color: lightgray;
            border-width: 2px;
            border-style: solid;
            border-color: black;
            margin: 2px;
        """)

        self.LabelGrid = Ui_Dialog()
        self.LabelGrid.setupUi(self)
        self.configureLabels()
        self.computeLabelPositions()



    def configureLabels(self):
        self.labels = [child for child in self.children() if child.__class__.__name__ == 'DragButton']
        for label in self.labels:
            if (isinstance(label,DragButton)) :
                    label.setSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.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

        #Upon Resizing of the window, the labels will also resize. Update the DragButton class variable that contains the position of each label.
        self.computeLabelPositions()



    #Add Label's position to 
    def computeLabelPositions(self):
        self.labels = [child for child in self.children() if child.__class__.__name__ == 'DragButton']
        for label in self.labels:
            if (isinstance(label,DragButton)) :
                DragButton.Positions[label] = label.mapToGlobal(label.geometry().topLeft()),label.mapToGlobal(label.geometry().bottomLeft())
                #print DragButton.Positions





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

DragButton Class in dragButton.py:

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

class DragButton(QtGui.QLabel):

    Positions = {}

    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
            self.currentDropZone = None
        super(DragButton, self).mousePressEvent(event)


    def mouseMoveEvent(self, event):
        if event.buttons() == QtCore.Qt.LeftButton:
            # adjust offset from clicked point to origin of widget
            self.currPos = self.mapToGlobal(self.pos())
            self.globalPos = event.globalPos()
            diff = self.globalPos - self.__mouseMovePos
            newPos = self.mapFromGlobal(self.currPos + diff)
            self.move(newPos)


            self.__mouseMovePos = self.globalPos
            if not self.start_move: 
                self.setStyleSheet("background-color: red;")

            if not self.start_move:
                self.start_move = 1

            self.checkIfInDropZone()

        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(self.currentDropZone):
                self.currentDropZone.move(self.mapFromGlobal(DragButton.Positions[self][0]))
                self.move(self.mapFromGlobal(DragButton.Positions[self.currentDropZone][0]))
                temp = DragButton.Positions[self.currentDropZone] 
                DragButton.Positions[self.currentDropZone] = DragButton.Positions[self]
                DragButton.Positions[self] = temp               

            else: 
                self.move(self.mapFromGlobal(DragButton.Positions[self][0]))

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

        super(DragButton, self).mouseReleaseEvent(event)

#Checks to see if the label being dragged is over a drop zone. If it is, the label that currently occupies the drop zone is stored in self.currentDropZone, and the color of that label is changed to green. 

    def checkIfInDropZone(self):
        validDropZone = 0   
        for key in DragButton.Positions:
            if key == self:
                continue
            if (self.currPos.y() > DragButton.Positions[key][0].y() and self.currPos.y() < DragButton.Positions[key][1].y()):
                key.setStyleSheet("background-color: lightgreen;")
                self.currentDropZone = key
                validDropZone = 1
            else:
                key.setStyleSheet("background-color: lightblue;")

        if(not validDropZone):
            self.currentDropZone = None

UI Class for the draggable labels in Drag.py:

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 dragButton import DragButton

回答1:


Use the coordinates of the button when it was released, apply some math or some sort of rounding to the coordinates, and programmatically move/re-draw the button to the rounded coordinates.

You will have to map out a grid with the spacing/dimensions you want (probably easiest on paper). Then figure out what the formula needs to be so that the x-coordinate is updated to the closest vertical line on the grid, and y-coordinate gets rounded to closest horizontal line on the grid.



来源:https://stackoverflow.com/questions/59218941/pyside-how-to-snap-a-button-into-a-place-when-i-drag-it

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