拖放按钮和下拉菜单 PyQt/Qt 设计师
Drag n Drop Button and Drop-down menu PyQt/Qt designer
我想知道 "best practice" 更改某些按钮的行为以执行以下操作:
我想通过点击显示一个菜单。或者,当您拖动同一个按钮时,您可以将它放到另一个按钮中,这将 "draw" 一条连接它们的线。
这是一个例子:
这个想法是将这些 "jack" 按钮连接到任何其他 "input" 按钮。
我正在使用 Qt 设计器,我意识到按钮属性只列出了 "acceptDrops" 属性,但我无法使其工作。
Signals/Slots 没有列出有关拖放的内容。
所以我认为唯一的方法是通过代码创建一个 "Custom Widget" 或 "reimplementing" 按钮。也许与 Signals/Slots
相同
如果我不想修改 pyuic 生成的文件,最好的方法是什么?
更新:我尝试的方法是使用 Qt 设计器和 "Promoted widgets" 选项。这允许我创建单独的 class 文件并重新实现一些元素。我已经通过将 PushButton 提升为 "DragButton" 并为其创建了 class 进行了测试:
从 PyQt4 导入 QtGui、QtCore
class 拖动按钮(QtGui.QPushButton):
def __init__(self, parent):
super(DragButton, self).__init__(parent)
self.allowDrag = True
def setAllowDrag(self, allowDrag):
if type(allowDrag) == bool:
self.allowDrag = allowDrag
else:
raise TypeError("You have to set a boolean type")
def mouseMoveEvent(self, e):
if e.buttons() != QtCore.Qt.RightButton:
return
if self.allowDrag == True:
# write the relative cursor position to mime data
mimeData = QtCore.QMimeData()
# simple string with 'x,y'
mimeData.setText('%d,%d' % (e.x(), e.y()))
print mimeData.text()
# let's make it fancy. we'll show a "ghost" of the button as we drag
# grab the button to a pixmap
pixmap = QtGui.QPixmap.grabWidget(self)
# below makes the pixmap half transparent
painter = QtGui.QPainter(pixmap)
painter.setCompositionMode(painter.CompositionMode_DestinationIn)
painter.fillRect(pixmap.rect(), QtGui.QColor(0, 0, 0, 127))
painter.end()
# make a QDrag
drag = QtGui.QDrag(self)
# put our MimeData
drag.setMimeData(mimeData)
# set its Pixmap
drag.setPixmap(pixmap)
# shift the Pixmap so that it coincides with the cursor position
drag.setHotSpot(e.pos())
# start the drag operation
# exec_ will return the accepted action from dropEvent
if drag.exec_(QtCore.Qt.LinkAction | QtCore.Qt.MoveAction) == QtCore.Qt.LinkAction:
print 'linked'
else:
print 'moved'
def mousePressEvent(self, e):
QtGui.QPushButton.mousePressEvent(self, e)
if e.button() == QtCore.Qt.LeftButton:
print 'press'
#AQUI DEBO IMPLEMENTAR EL MENU CONTEXTUAL
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
# get the relative position from the mime data
mime = e.mimeData().text()
x, y = map(int, mime.split(','))
# move
# so move the dragged button (i.e. event.source())
print e.pos()
#e.source().move(e.pos()-QtCore.QPoint(x, y))
# set the drop action as LinkAction
e.setDropAction(QtCore.Qt.LinkAction)
# tell the QDrag we accepted it
e.accept()
我得到了一些提示并从中摘录了一些片段 post:
PyQt4 - Drag and Drop
此时我可以拖动此按钮,并将其放入另一个在 Qt 设计器中将 "acceptDrops" 属性 设置为 true 的相同类型。 但是,我仍然想限制某些按钮的拖动(可能通过在主文件中使用 UpdateUi 方法进行设置),因为有些按钮仅用于接受掉落
更新 2: 现在我正在尝试编写 class 绘制线条或 "wires" 连接这些按钮。
我试图在两个小部件(两个按钮)之间画一条线到 graphicsView 中,并以它们的位置作为参考。但是当我尝试时,线画错了地方。我也尝试过使用像 mapToGlobal 或 mapToParent 这样的函数得到不同的结果,但仍然是错误的。
在同一个 class 中,我有另一种用鼠标画线的方法,并且工作正常。我把它当作一个参考或例子,但似乎事件位置有不同的坐标系。好吧,我不知道为什么会这样。
按钮和图形视图在 Widget 内,Widget 也在 Window.
内
这里是class,我们说的方法是
从 PyQt4 导入 QtGui,QtCore
class WiringGraphicsView(QtGui.QGraphicsView):
def __init__(self, parent):
QtGui.QGraphicsView.__init__(self, parent)
self.setScene(QtGui.QGraphicsScene(self))
self.setSceneRect(QtCore.QRectF(self.viewport().rect()))
def mousePressEvent(self, event):
self._start = event.pos()
def mouseReleaseEvent(self, event):
start = QtCore.QPointF(self.mapToScene(self._start))
end = QtCore.QPointF(self.mapToScene(event.pos()))
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
pen = QtGui.QPen(brush, 2)
line = QtGui.QGraphicsLineItem(QtCore.QLineF(start, end))
line.setPen(pen)
self.scene().addItem( line )
def paintWire(self, start_widget, end_widget):
start_position = QtCore.QPointF(self.mapToScene(start_widget.pos()))
end_position = QtCore.QPointF(self.mapToScene(end_widget.pos()))
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
pen = QtGui.QPen(brush, 2)
line = QtGui.QGraphicsLineItem(QtCore.QLineF(start_position, end_position))
line.setPen(pen)
self.scene().addItem( line )
如果有更好的实现方法,请告诉我。
我的感觉是,您可以尝试使用标准 QWidget
来实现此目的,但使用 QGraphicsScene
/QGraphicsView
API.[=17 会更容易=]
另请注意,您可以使用 QGraphicsProxyWidget
.
在 QGraphicsScene
中嵌入 QWidget
如果你想在按钮之间画一条线,这意味着你需要重新实现背景小部件的"paintEvent"(可能是所有子小部件的父小部件),正如你所说,这不是这样做的最佳实践。相反,您需要使用 QGraphicsWidget,对于画线,您需要使用 QGraphicsLineItem。它有以下成员函数:
setAcceptDrops
dragEnterEvent ( QGraphicsSceneDragDropEvent * )
dragLeaveEvent ( QGraphicsSceneDragDropEvent * )
dragMoveEvent ( QGraphicsSceneDragDropEvent * )
在PyQt4的安装文件夹中,应该有一个名为examples\graphicsview\diagramscene的文件夹,大家可以参考一下。
您需要使用 QDropEvent,我知道这不是一个很好的答案,但只需创建一个 QDropEvent 函数并在该函数中检查放置的按钮。
如果 firstButton 落在 secondButton 上,painter->drawLine(firstButton.pos(), secondButton.pos());
您可以使用其他点来画线。或者您可以对拖动的按钮使用 event->source()
。您可能需要使用一些设置来定义 QPen。当我说其他要使用的点时,我的意思是像 firstButton.boundingRect().topRight().x(), firstButton.boundingRect.bottomRight().y() - firstButton.boundingRect.height() / 2
参见:http://doc.qt.io/qt-5/qdropevent.html
抱歉,此代码是伪 C++ 代码,但您可以轻松地将其改编为 Python。
示例:
void MainWindow::dropEvent(QDropEvent *event)
{
if(event->pos() == somePoint) //somePoint should be inside target's boundingRect, you need to write basic collision detection
painter->drawLine(event->source().pos(), event->pos()); //You might use other points
}
还有其他拖放事件。您可以更改目标的颜色,例如,如果将其拖动到目标上。参见 http://doc.qt.io/qt-5/dnd.html
如果您需要,我可以尝试帮助您编写碰撞检测代码。我不知道是否有更好的方法。可能有。我主要是 C++ 编码员,但我可以提供基本示例。
还有下拉菜单。您可以使用带有一些 mouseEvents 的菜单创建一个简单的 QWidget,并使其成为按钮的子级,并设置它的 y 位置,使其显示在按钮下方。例如(同样,C++,抱歉):
dropDownMenu->setParent(someButton);
dropDownMenu->setPos(someButton.x(), someButton.y() + someButton.boundingRect().height());
您可以使用 mouseReleaseEvent 隐藏或显示它。只需确保将其隐藏在您的 dropEvent 函数中,这样当您拖放它时,它就不会显示。
编辑:让我向您展示一个简单的 C++ 碰撞检测代码,但很容易适应 Python。
if(event->pos() > target.boundingRect().topLeft().x() && event->pos() < target.topRight.x() && event->pos() > target.boundingRect().topRight() && event->pos() < target.boundingRect().bottomRight()) {
//It's on the button.
}
如果你想要一个更简单的解决方案。只需子 class 您要放置到的按钮并在它们的 class 中添加拖放事件。这会更容易,也更短。我认为 dropEvent 也应该在 subclass 中工作。我没试过。
编辑:如果您询问如何仅使用 Qt Designer 来完成所有这些操作,那么您做不到。你需要写一些代码。你不能用 Qt Designer 开发程序,它只是为了让 ui 更容易。没有 Qt Designer 可以制作软件,但是 仅 Qt Designer 无法制作软件。您需要学习一些 Python 编程知识和一些 PyQt 才能完成此类任务。但是 Python 和 Qt 都很容易在短时间内掌握它们,而且 Qt 文档非常棒。
祝你好运!
为了向使用 QtDesigner 生成的 UI 添加代码,您必须使用 pyuic:[=16 生成 .py 文件=]
pyuic myform.ui -o ui_myform.py
此 ui_myform.py 文件包含您 不应编辑 的生成代码,因此稍后您可以使用 QtDesigner 更改您的 .ui 文件,重新运行 pyuic,并在不丢失任何工作的情况下更新 ui_myform.py。
生成的文件将有一个 class Ui_myForm(object)
(以您的主窗口小部件的名称命名),其中包含一个 def setupUi(self, myForm)
方法。可以使用的一种方法是创建自己的 class MyForm
(在单独的文件中),它将继承 Ui_myForm 和其他一些 Qt Class,如 QWidget 或 QDialog:
myform.py:
from ui_myform import Ui_myForm
from PyQt4.QtGui import QDialog
class MyForm(QDialog, Ui_myForm):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self) #here Ui_myForm creates all widgets as members
#of this object.
#now you can access every widget defined in
#myForm as attributes of self
#supposing you defined two pushbuttons on your .UI file:
self.pushButtonB.setEnabled(False)
#you can connect signals of the generated widgets
self.pushButtonA.clicked.connect(self.pushButtonAClicked)
def bucar_actualizaciones(self):
self.pushButtonB.setEnabled(True)
小部件的名称是您在 QtDesigner 上设置的名称,但很容易检查ui_myform.py以便查看可用的小部件和名称。
要在 QtDesigner 中使用自定义小部件,您可以右键单击该按钮,然后转到升级到...。在那里你必须输入:
- 基础class名称:例如QPushButton
- 提升class名称:MyPushButton(这必须是您的自定义小部件class的名称)
- 头文件:mypushbutton.h。这将通过 pyuic.
转换为 .py
单击添加,然后单击升级。
当你运行 pyuic时,它会在ui_myform.py
末尾添加这一行
from mypushbutton import MyPushButton
此外,您会看到生成的代码使用了 MyPushButton 而不是 QPushButton
我想知道 "best practice" 更改某些按钮的行为以执行以下操作:
我想通过点击显示一个菜单。或者,当您拖动同一个按钮时,您可以将它放到另一个按钮中,这将 "draw" 一条连接它们的线。
这是一个例子:
我正在使用 Qt 设计器,我意识到按钮属性只列出了 "acceptDrops" 属性,但我无法使其工作。 Signals/Slots 没有列出有关拖放的内容。
所以我认为唯一的方法是通过代码创建一个 "Custom Widget" 或 "reimplementing" 按钮。也许与 Signals/Slots
相同如果我不想修改 pyuic 生成的文件,最好的方法是什么?
更新:我尝试的方法是使用 Qt 设计器和 "Promoted widgets" 选项。这允许我创建单独的 class 文件并重新实现一些元素。我已经通过将 PushButton 提升为 "DragButton" 并为其创建了 class 进行了测试:
从 PyQt4 导入 QtGui、QtCore
class 拖动按钮(QtGui.QPushButton):
def __init__(self, parent):
super(DragButton, self).__init__(parent)
self.allowDrag = True
def setAllowDrag(self, allowDrag):
if type(allowDrag) == bool:
self.allowDrag = allowDrag
else:
raise TypeError("You have to set a boolean type")
def mouseMoveEvent(self, e):
if e.buttons() != QtCore.Qt.RightButton:
return
if self.allowDrag == True:
# write the relative cursor position to mime data
mimeData = QtCore.QMimeData()
# simple string with 'x,y'
mimeData.setText('%d,%d' % (e.x(), e.y()))
print mimeData.text()
# let's make it fancy. we'll show a "ghost" of the button as we drag
# grab the button to a pixmap
pixmap = QtGui.QPixmap.grabWidget(self)
# below makes the pixmap half transparent
painter = QtGui.QPainter(pixmap)
painter.setCompositionMode(painter.CompositionMode_DestinationIn)
painter.fillRect(pixmap.rect(), QtGui.QColor(0, 0, 0, 127))
painter.end()
# make a QDrag
drag = QtGui.QDrag(self)
# put our MimeData
drag.setMimeData(mimeData)
# set its Pixmap
drag.setPixmap(pixmap)
# shift the Pixmap so that it coincides with the cursor position
drag.setHotSpot(e.pos())
# start the drag operation
# exec_ will return the accepted action from dropEvent
if drag.exec_(QtCore.Qt.LinkAction | QtCore.Qt.MoveAction) == QtCore.Qt.LinkAction:
print 'linked'
else:
print 'moved'
def mousePressEvent(self, e):
QtGui.QPushButton.mousePressEvent(self, e)
if e.button() == QtCore.Qt.LeftButton:
print 'press'
#AQUI DEBO IMPLEMENTAR EL MENU CONTEXTUAL
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
# get the relative position from the mime data
mime = e.mimeData().text()
x, y = map(int, mime.split(','))
# move
# so move the dragged button (i.e. event.source())
print e.pos()
#e.source().move(e.pos()-QtCore.QPoint(x, y))
# set the drop action as LinkAction
e.setDropAction(QtCore.Qt.LinkAction)
# tell the QDrag we accepted it
e.accept()
我得到了一些提示并从中摘录了一些片段 post: PyQt4 - Drag and Drop
此时我可以拖动此按钮,并将其放入另一个在 Qt 设计器中将 "acceptDrops" 属性 设置为 true 的相同类型。 但是,我仍然想限制某些按钮的拖动(可能通过在主文件中使用 UpdateUi 方法进行设置),因为有些按钮仅用于接受掉落
更新 2: 现在我正在尝试编写 class 绘制线条或 "wires" 连接这些按钮。
我试图在两个小部件(两个按钮)之间画一条线到 graphicsView 中,并以它们的位置作为参考。但是当我尝试时,线画错了地方。我也尝试过使用像 mapToGlobal 或 mapToParent 这样的函数得到不同的结果,但仍然是错误的。 在同一个 class 中,我有另一种用鼠标画线的方法,并且工作正常。我把它当作一个参考或例子,但似乎事件位置有不同的坐标系。好吧,我不知道为什么会这样。
按钮和图形视图在 Widget 内,Widget 也在 Window.
内这里是class,我们说的方法是 从 PyQt4 导入 QtGui,QtCore
class WiringGraphicsView(QtGui.QGraphicsView):
def __init__(self, parent):
QtGui.QGraphicsView.__init__(self, parent)
self.setScene(QtGui.QGraphicsScene(self))
self.setSceneRect(QtCore.QRectF(self.viewport().rect()))
def mousePressEvent(self, event):
self._start = event.pos()
def mouseReleaseEvent(self, event):
start = QtCore.QPointF(self.mapToScene(self._start))
end = QtCore.QPointF(self.mapToScene(event.pos()))
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
pen = QtGui.QPen(brush, 2)
line = QtGui.QGraphicsLineItem(QtCore.QLineF(start, end))
line.setPen(pen)
self.scene().addItem( line )
def paintWire(self, start_widget, end_widget):
start_position = QtCore.QPointF(self.mapToScene(start_widget.pos()))
end_position = QtCore.QPointF(self.mapToScene(end_widget.pos()))
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0) )
pen = QtGui.QPen(brush, 2)
line = QtGui.QGraphicsLineItem(QtCore.QLineF(start_position, end_position))
line.setPen(pen)
self.scene().addItem( line )
如果有更好的实现方法,请告诉我。
我的感觉是,您可以尝试使用标准 QWidget
来实现此目的,但使用 QGraphicsScene
/QGraphicsView
API.[=17 会更容易=]
另请注意,您可以使用 QGraphicsProxyWidget
.
QGraphicsScene
中嵌入 QWidget
如果你想在按钮之间画一条线,这意味着你需要重新实现背景小部件的"paintEvent"(可能是所有子小部件的父小部件),正如你所说,这不是这样做的最佳实践。相反,您需要使用 QGraphicsWidget,对于画线,您需要使用 QGraphicsLineItem。它有以下成员函数:
setAcceptDrops
dragEnterEvent ( QGraphicsSceneDragDropEvent * )
dragLeaveEvent ( QGraphicsSceneDragDropEvent * )
dragMoveEvent ( QGraphicsSceneDragDropEvent * )
在PyQt4的安装文件夹中,应该有一个名为examples\graphicsview\diagramscene的文件夹,大家可以参考一下。
您需要使用 QDropEvent,我知道这不是一个很好的答案,但只需创建一个 QDropEvent 函数并在该函数中检查放置的按钮。
如果 firstButton 落在 secondButton 上,painter->drawLine(firstButton.pos(), secondButton.pos());
您可以使用其他点来画线。或者您可以对拖动的按钮使用 event->source()
。您可能需要使用一些设置来定义 QPen。当我说其他要使用的点时,我的意思是像 firstButton.boundingRect().topRight().x(), firstButton.boundingRect.bottomRight().y() - firstButton.boundingRect.height() / 2
参见:http://doc.qt.io/qt-5/qdropevent.html
抱歉,此代码是伪 C++ 代码,但您可以轻松地将其改编为 Python。
示例:
void MainWindow::dropEvent(QDropEvent *event)
{
if(event->pos() == somePoint) //somePoint should be inside target's boundingRect, you need to write basic collision detection
painter->drawLine(event->source().pos(), event->pos()); //You might use other points
}
还有其他拖放事件。您可以更改目标的颜色,例如,如果将其拖动到目标上。参见 http://doc.qt.io/qt-5/dnd.html
如果您需要,我可以尝试帮助您编写碰撞检测代码。我不知道是否有更好的方法。可能有。我主要是 C++ 编码员,但我可以提供基本示例。
还有下拉菜单。您可以使用带有一些 mouseEvents 的菜单创建一个简单的 QWidget,并使其成为按钮的子级,并设置它的 y 位置,使其显示在按钮下方。例如(同样,C++,抱歉):
dropDownMenu->setParent(someButton);
dropDownMenu->setPos(someButton.x(), someButton.y() + someButton.boundingRect().height());
您可以使用 mouseReleaseEvent 隐藏或显示它。只需确保将其隐藏在您的 dropEvent 函数中,这样当您拖放它时,它就不会显示。
编辑:让我向您展示一个简单的 C++ 碰撞检测代码,但很容易适应 Python。
if(event->pos() > target.boundingRect().topLeft().x() && event->pos() < target.topRight.x() && event->pos() > target.boundingRect().topRight() && event->pos() < target.boundingRect().bottomRight()) {
//It's on the button.
}
如果你想要一个更简单的解决方案。只需子 class 您要放置到的按钮并在它们的 class 中添加拖放事件。这会更容易,也更短。我认为 dropEvent 也应该在 subclass 中工作。我没试过。
编辑:如果您询问如何仅使用 Qt Designer 来完成所有这些操作,那么您做不到。你需要写一些代码。你不能用 Qt Designer 开发程序,它只是为了让 ui 更容易。没有 Qt Designer 可以制作软件,但是 仅 Qt Designer 无法制作软件。您需要学习一些 Python 编程知识和一些 PyQt 才能完成此类任务。但是 Python 和 Qt 都很容易在短时间内掌握它们,而且 Qt 文档非常棒。
祝你好运!
为了向使用 QtDesigner 生成的 UI 添加代码,您必须使用 pyuic:[=16 生成 .py 文件=]
pyuic myform.ui -o ui_myform.py
此 ui_myform.py 文件包含您 不应编辑 的生成代码,因此稍后您可以使用 QtDesigner 更改您的 .ui 文件,重新运行 pyuic,并在不丢失任何工作的情况下更新 ui_myform.py。
生成的文件将有一个 class Ui_myForm(object)
(以您的主窗口小部件的名称命名),其中包含一个 def setupUi(self, myForm)
方法。可以使用的一种方法是创建自己的 class MyForm
(在单独的文件中),它将继承 Ui_myForm 和其他一些 Qt Class,如 QWidget 或 QDialog:
myform.py:
from ui_myform import Ui_myForm
from PyQt4.QtGui import QDialog
class MyForm(QDialog, Ui_myForm):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self) #here Ui_myForm creates all widgets as members
#of this object.
#now you can access every widget defined in
#myForm as attributes of self
#supposing you defined two pushbuttons on your .UI file:
self.pushButtonB.setEnabled(False)
#you can connect signals of the generated widgets
self.pushButtonA.clicked.connect(self.pushButtonAClicked)
def bucar_actualizaciones(self):
self.pushButtonB.setEnabled(True)
小部件的名称是您在 QtDesigner 上设置的名称,但很容易检查ui_myform.py以便查看可用的小部件和名称。
要在 QtDesigner 中使用自定义小部件,您可以右键单击该按钮,然后转到升级到...。在那里你必须输入:
- 基础class名称:例如QPushButton
- 提升class名称:MyPushButton(这必须是您的自定义小部件class的名称)
- 头文件:mypushbutton.h。这将通过 pyuic. 转换为 .py
单击添加,然后单击升级。
当你运行 pyuic时,它会在ui_myform.py
末尾添加这一行from mypushbutton import MyPushButton
此外,您会看到生成的代码使用了 MyPushButton 而不是 QPushButton