PyQt:是否可以 drag/drop QGridLayout 中的 QWidgets 来重新排列它们?
PyQt: Is it possible to drag/drop QWidgets in a QGridLayout to rearrange them?
我正在寻找一种方法来创建可以 dragged/dropped 重新排列顺序的图形网格。我的第一次尝试是使用 QDockWidgets,因为它们允许 drag/drop,但是它们在许多其他方面受到限制。是否可以在 QGridLayout 中实现此功能?
现在我有一个带有 3x3 matplotlib 小部件的 QGridLayout。
这是所需布局结果的示例。
示例代码:
import sys
from PyQt5 import QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import random
from PyQt5.QtWidgets import QGridLayout, QVBoxLayout, QHBoxLayout, QScrollArea, QWidget, QDialog, QApplication, QFrame
class IndicSelectWindow(QDialog):
def __init__(self, parent=None):
super(IndicSelectWindow, self).__init__(parent=parent)
self.resize(1000, 800)
self.layout = QtWidgets.QHBoxLayout(self)
self.scrollArea = QScrollArea(self)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.layout.addWidget(self.scrollArea)
for i in range(3):
for j in range(3):
self.Frame = QFrame(self)
self.Frame.setStyleSheet("background-color: white;")
self.Frame.setFrameStyle(QFrame.Panel | QFrame.Raised)
self.Frame.setLineWidth(2)
self.layout = QHBoxLayout(self.Frame)
self.figure = Figure() # a figure to plot on
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111) # create an axis
data = [random.random() for i in range(10)]
self.ax.plot(data, '*-') # plot data
self.canvas.draw() # refresh canvas
self.layout.addWidget(self.canvas)
Box = QVBoxLayout()
Box.addWidget(self.Frame)
self.gridLayout.addLayout(Box, i, j)
self.gridLayout.setColumnStretch(i % 3, 1)
self.gridLayout.setRowStretch(j, 1)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = IndicSelectWindow()
w.show()
sys.exit(app.exec_())
这里是一个实现,将交换 drag/drop 中涉及的项目的位置。 3 个主要步骤是:
(1) 重新实现mousePressEvent
,根据鼠标坐标获取LayoutItem
的索引。
(2) 重新实现 mouseMoveEvent
以设置 FigureCanvas 的 QDrag
。
(3) 重新实现 dropEvent
以交换布局中的目标项。
由于 matplotlib 小部件吸收鼠标事件,您还需要重新实现 eventFilter
来检测它们。
import sys, random
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class IndicSelectWindow(QDialog):
def __init__(self, parent=None):
super(IndicSelectWindow, self).__init__(parent=parent)
self.resize(1000, 800)
self.target = None
self.setAcceptDrops(True)
self.layout = QHBoxLayout(self)
self.scrollArea = QScrollArea(self)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.layout.addWidget(self.scrollArea)
for i in range(3):
for j in range(3):
self.Frame = QFrame(self)
self.Frame.setStyleSheet("background-color: white;")
self.Frame.setFrameStyle(QFrame.Panel | QFrame.Raised)
self.Frame.setLineWidth(2)
self.layout = QHBoxLayout(self.Frame)
self.figure = Figure() # a figure to plot on
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111) # create an axis
data = [random.random() for i in range(10)]
self.ax.plot(data, '*-') # plot data
self.canvas.draw() # refresh canvas
self.canvas.installEventFilter(self)
self.layout.addWidget(self.canvas)
Box = QVBoxLayout()
Box.addWidget(self.Frame)
self.gridLayout.addLayout(Box, i, j)
self.gridLayout.setColumnStretch(i % 3, 1)
self.gridLayout.setRowStretch(j, 1)
def eventFilter(self, watched, event):
if event.type() == QEvent.MouseButtonPress:
self.mousePressEvent(event)
elif event.type() == QEvent.MouseMove:
self.mouseMoveEvent(event)
elif event.type() == QEvent.MouseButtonRelease:
self.mouseReleaseEvent(event)
return super().eventFilter(watched, event)
def get_index(self, pos):
for i in range(self.gridLayout.count()):
if self.gridLayout.itemAt(i).geometry().contains(pos) and i != self.target:
return i
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.target = self.get_index(event.windowPos().toPoint())
else:
self.target = None
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton and self.target is not None:
drag = QDrag(self.gridLayout.itemAt(self.target))
pix = self.gridLayout.itemAt(self.target).itemAt(0).widget().grab()
mimedata = QMimeData()
mimedata.setImageData(pix)
drag.setMimeData(mimedata)
drag.setPixmap(pix)
drag.setHotSpot(event.pos())
drag.exec_()
def mouseReleaseEvent(self, event):
self.target = None
def dragEnterEvent(self, event):
if event.mimeData().hasImage():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if not event.source().geometry().contains(event.pos()):
source = self.get_index(event.pos())
if source is None:
return
i, j = max(self.target, source), min(self.target, source)
p1, p2 = self.gridLayout.getItemPosition(i), self.gridLayout.getItemPosition(j)
self.gridLayout.addItem(self.gridLayout.takeAt(i), *p2)
self.gridLayout.addItem(self.gridLayout.takeAt(j), *p1)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = IndicSelectWindow()
w.show()
sys.exit(app.exec_())
我正在寻找一种方法来创建可以 dragged/dropped 重新排列顺序的图形网格。我的第一次尝试是使用 QDockWidgets,因为它们允许 drag/drop,但是它们在许多其他方面受到限制。是否可以在 QGridLayout 中实现此功能?
现在我有一个带有 3x3 matplotlib 小部件的 QGridLayout。
这是所需布局结果的示例。
示例代码:
import sys
from PyQt5 import QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import random
from PyQt5.QtWidgets import QGridLayout, QVBoxLayout, QHBoxLayout, QScrollArea, QWidget, QDialog, QApplication, QFrame
class IndicSelectWindow(QDialog):
def __init__(self, parent=None):
super(IndicSelectWindow, self).__init__(parent=parent)
self.resize(1000, 800)
self.layout = QtWidgets.QHBoxLayout(self)
self.scrollArea = QScrollArea(self)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.layout.addWidget(self.scrollArea)
for i in range(3):
for j in range(3):
self.Frame = QFrame(self)
self.Frame.setStyleSheet("background-color: white;")
self.Frame.setFrameStyle(QFrame.Panel | QFrame.Raised)
self.Frame.setLineWidth(2)
self.layout = QHBoxLayout(self.Frame)
self.figure = Figure() # a figure to plot on
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111) # create an axis
data = [random.random() for i in range(10)]
self.ax.plot(data, '*-') # plot data
self.canvas.draw() # refresh canvas
self.layout.addWidget(self.canvas)
Box = QVBoxLayout()
Box.addWidget(self.Frame)
self.gridLayout.addLayout(Box, i, j)
self.gridLayout.setColumnStretch(i % 3, 1)
self.gridLayout.setRowStretch(j, 1)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = IndicSelectWindow()
w.show()
sys.exit(app.exec_())
这里是一个实现,将交换 drag/drop 中涉及的项目的位置。 3 个主要步骤是:
(1) 重新实现mousePressEvent
,根据鼠标坐标获取LayoutItem
的索引。
(2) 重新实现 mouseMoveEvent
以设置 FigureCanvas 的 QDrag
。
(3) 重新实现 dropEvent
以交换布局中的目标项。
由于 matplotlib 小部件吸收鼠标事件,您还需要重新实现 eventFilter
来检测它们。
import sys, random from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * from matplotlib.figure import Figure from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas class IndicSelectWindow(QDialog): def __init__(self, parent=None): super(IndicSelectWindow, self).__init__(parent=parent) self.resize(1000, 800) self.target = None self.setAcceptDrops(True) self.layout = QHBoxLayout(self) self.scrollArea = QScrollArea(self) self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() self.gridLayout = QGridLayout(self.scrollAreaWidgetContents) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.layout.addWidget(self.scrollArea) for i in range(3): for j in range(3): self.Frame = QFrame(self) self.Frame.setStyleSheet("background-color: white;") self.Frame.setFrameStyle(QFrame.Panel | QFrame.Raised) self.Frame.setLineWidth(2) self.layout = QHBoxLayout(self.Frame) self.figure = Figure() # a figure to plot on self.canvas = FigureCanvas(self.figure) self.ax = self.figure.add_subplot(111) # create an axis data = [random.random() for i in range(10)] self.ax.plot(data, '*-') # plot data self.canvas.draw() # refresh canvas self.canvas.installEventFilter(self) self.layout.addWidget(self.canvas) Box = QVBoxLayout() Box.addWidget(self.Frame) self.gridLayout.addLayout(Box, i, j) self.gridLayout.setColumnStretch(i % 3, 1) self.gridLayout.setRowStretch(j, 1) def eventFilter(self, watched, event): if event.type() == QEvent.MouseButtonPress: self.mousePressEvent(event) elif event.type() == QEvent.MouseMove: self.mouseMoveEvent(event) elif event.type() == QEvent.MouseButtonRelease: self.mouseReleaseEvent(event) return super().eventFilter(watched, event) def get_index(self, pos): for i in range(self.gridLayout.count()): if self.gridLayout.itemAt(i).geometry().contains(pos) and i != self.target: return i def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.target = self.get_index(event.windowPos().toPoint()) else: self.target = None def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton and self.target is not None: drag = QDrag(self.gridLayout.itemAt(self.target)) pix = self.gridLayout.itemAt(self.target).itemAt(0).widget().grab() mimedata = QMimeData() mimedata.setImageData(pix) drag.setMimeData(mimedata) drag.setPixmap(pix) drag.setHotSpot(event.pos()) drag.exec_() def mouseReleaseEvent(self, event): self.target = None def dragEnterEvent(self, event): if event.mimeData().hasImage(): event.accept() else: event.ignore() def dropEvent(self, event): if not event.source().geometry().contains(event.pos()): source = self.get_index(event.pos()) if source is None: return i, j = max(self.target, source), min(self.target, source) p1, p2 = self.gridLayout.getItemPosition(i), self.gridLayout.getItemPosition(j) self.gridLayout.addItem(self.gridLayout.takeAt(i), *p2) self.gridLayout.addItem(self.gridLayout.takeAt(j), *p1) if __name__ == '__main__': app = QApplication(sys.argv) w = IndicSelectWindow() w.show() sys.exit(app.exec_())