PyQt5:添加lineEdit的子类QSlider(用于在QtDesigner中提升)?
Posted
技术标签:
【中文标题】PyQt5:添加lineEdit的子类QSlider(用于在QtDesigner中提升)?【英文标题】:PyQt5: subclassing QSlider (for promote in QtDesigner) with added lineEdit? 【发布时间】:2020-07-20 15:20:22 【问题描述】:基本上,我想在 QtDesigner 的窗口中添加一些 QSlider,然后将它们“替换”为一个包含滑块的类,并在其下方包含一个文本框(行编辑)。
编辑:我为什么要这样做:当我使用 QtDesigner 时,我可以放置滑块,并对布局进行近似可视化,因为它将在最终应用程序中:
这就是为什么我想使用 QtDesigner 开始 - 获得最终布局的近似可视化,因为它将在应用程序中。而且由于我想用某种基于滑块的小部件替换这些滑块,因此首先将滑块放在视图中对我来说更有帮助。
但是,如果我必须将 QWidget 作为替换滑块所在位置的起点,那么 QtDesigner 视图如下所示:
换句话说,以前显示滑块的空间现在是空的 - 所以现在我不再预览最终的 GUI 布局,这有点违背了我使用 QtDesigner 的目的(我可能会好好努力,尝试完全在代码中绘制 GUI,没有任何视觉反馈,可能需要花费所有时间)。
到目前为止,我设法做到了这一点 - 我在 test2.py
中实现了一个名为“VertSlider”的 QSlider 子类,然后在 test2.ui
中将 QtDesigner 中的 QSlider 提升为此类:
有趣的是,它有点效果 - 如果您努力观察右侧两个滑块的中心,您可以在滑块的中心看到线条编辑的轮廓。
但显然我不希望这样 - 我希望在底部进行行编辑,它应该根据需要从原始滑块外观(如 QtDesigner 中指定)中占用尽可能多的垂直空间,然后实际的滑块应该填满其余的垂直空间(如屏幕截图左侧所示)。
我想,部分问题是,QSlider 似乎没有.layout()
- 默认情况下返回“无”;我试图强迫一个,但这显然不起作用。
通过Qt widget stacking child layouts on top of each other 找到documentation 的这句话:
如果这个小部件上已经安装了一个布局管理器,QWidget 不会让你安装另一个。您必须先删除现有的布局管理器(由 layout() 返回),然后才能使用新布局调用 setLayout()。
显然,QSlider 没有默认布局管理器 .... 从 Is it possible to add text on top of a scrollbar? 和 Qt add a widget inside another widget? 判断 - 在这种情况下,我必须要么“子类 ... 并覆盖 paintEvent( )" 或 "使用代理样式/drawComplexControl()
";根据https://www.learnpyqt.com/courses/custom-widgets/creating-your-own-custom-widgets/ 中的术语,这将是一个“自定义绘制的小部件”,但我真的希望我可以“只是”做一个“复合”小部件:只是以某种方式从 QtDesigner 中基于 QSlider 的规范 -> 到一个 QSLider+QLineEdit 小部件,无需处理自定义绘画。
当然,原则上我可以将 QWidget 子类化,然后使用 layout.addWidget
的方法会起作用 - 但我不能使用该子类来“提升”作为 QtDesigner 中的 QSlider 放置的内容。
那么,创建一个 QSlider 子类最简单的方法是什么,它只需在滑块底部添加一个行编辑文本框,它可以用作在 QtDesigner 中提升 QSlider 的类?
test2.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>436</width>
<height>354</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Want this:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item alignment="Qt::AlignHCenter">
<widget class="QSlider" name="verticalSlider">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QLineEdit" name="lineEdit">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>... by promoting these QSliders in QtDesigner:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="VertSlider" name="verticalSlider_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="VertSlider" name="verticalSlider_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>436</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>VertSlider</class>
<extends>QSlider</extends>
<header>test2</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
test2.py
import sys
from PyQt5 import QtCore, QtWidgets, QtGui, uic
from PyQt5.QtCore import pyqtSlot
class VertSlider(QtWidgets.QSlider):
def __init__(self, *args, **kwargs):
QtWidgets.QSlider.__init__(self, *args, **kwargs)
print(self.layout()) # None
# so, trying to force a layout here, so I could add a line edit - but it doesn't quite work:
self.layout = QtWidgets.QVBoxLayout(self)
self.label = QtWidgets.QLineEdit(self)
self.label.setText("aa")
self.label.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
self.layout.addWidget(self.label)
self.setLayout(self.layout)
print(self.layout, self.layout.count(), self.label.width(), self.label.height(), self.label.x(), self.label.y()) # <PyQt5.QtWidgets.QVBoxLayout object at 0x0000000006681790> 1 100 30 0 0
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi('test2.ui', self)
self.show()
def main():
app = QtWidgets.QApplication(sys.argv)
window = MyMainWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
【问题讨论】:
@eyllanesc 答案是正确的。关键是使用 QWidget 作为 QSlider 和 QLineEdit 子小部件的容器。您可以在 Qt Designer 中使用它的子小部件绘制 QWidget,然后将其提升为您的自定义小部件。 感谢@bfris - 我终于设法在 QtDesigner 中使用了“在 Qt Designer 中使用它的子小部件绘制 QWidget”方法,它允许在 QtDesigner 中进行不错的预览,并使用@eyllanesc 答案 - 在下面发布了一个单独的答案。 【参考方案1】:您不想推广 QSlider 而是推广包含 QSlider 的类,因此解决方案是创建该小部件:
import sys
from PyQt5 import QtCore, QtWidgets, QtGui, uic
class CustomWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.slider = QtWidgets.QSlider(orientation=QtCore.Qt.Vertical)
self.lineedit = QtWidgets.QLineEdit(text="aa")
# self.lineedit.setMaximumWidth(50)
hlay = QtWidgets.QHBoxLayout(self)
vlay = QtWidgets.QVBoxLayout()
vlay.addWidget(self.slider, 0, QtCore.Qt.AlignHCenter)
vlay.addWidget(self.lineedit, 0, QtCore.Qt.AlignHCenter)
hlay.addLayout(vlay)
spacer_item = QtWidgets.QSpacerItem(
40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
)
hlay.addItem(spacer_item)
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi("test2.ui", self)
self.show()
def main():
app = QtWidgets.QApplication(sys.argv)
window = MyMainWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>436</width>
<height>354</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Want this:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item alignment="Qt::AlignHCenter">
<widget class="QSlider" name="verticalSlider">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QLineEdit" name="lineEdit">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>... by promoting these QSliders in QtDesigner:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="CustomWidget" name="widget_2" native="true"/>
</item>
<item>
<widget class="CustomWidget" name="widget" native="true"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>436</width>
<height>26</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>CustomWidget</class>
<extends>QWidget</extends>
<header>test2</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
【讨论】:
感谢@eyllanesc,+1 - 这确实适用于 Python,但是,它会导致 QtDesigner 中的小部件为空,这使我更难以直观地设计 GUI(请参阅我的 OP 编辑) .但是,我也尝试利用@bfris 的评论 - 并想出了一种方法来使用您的解决方案,并在 QtDesigner 中有可见的滑块;将其作为单独的答案发布。 @sdbbs 我建议您重新阅读您的问题,因为您无处表明您想在 Qt Designer 中显示小部件,您清楚地表明问题是您提升的小部件不起作用,所以从我的观点是我的答案是正确的,显然其他事情可以建立在答案的基础上,但我只关注 OP 的字面要求,而不是别的。如果我专注于不明确的事情,那么我永远写不完答案 谢谢,@eyllanesc - 你完全正确,对于忘记提及该要求,我深表歉意。【参考方案2】:我可能早该说过,在 QtDesigner 中从滑块图像开始对我来说很重要,因为它可以帮助我直观地设计 GUI 界面。
@eyllanesc 的解决方案在技术上有效 - 但是,我在 QtDesigner 中有空的小部件,这对我的视觉设计没有帮助。
但是,我从@bfris 的评论中尝试了这个建议:
您可以在 Qt Designer 中使用它的子小部件绘制 QWidget,然后将其提升为您的自定义小部件
...我想我得到了一个混合了@eyllanesc 的答案和这种方法的解决方案,所以我既可以在 QtDesigner 中看到滑块,也可以将 QWidget 子类化。
首先,在添加了一个小部件之后(我只是使用来自@eyllanesc 答案的 .ui 作为起点),只需在 QtDesigner 中拖动一个垂直滑块作为其子项:
然而,此时新添加的滑块不会“对齐”,因为承载它的小部件(父小部件)没有布局(如底部的红色圆形叉号图标所示QtDesigner 的 Object Inspector 树视图中的小部件图标右侧)。这里只需要在Object Inspector中右击父widget,选择一个布局(这里我选择的是水平布局):
完成后,小部件图标右下角的红色圆形叉掉图标消失,滑块在Qt Designer中看起来很合理:
现在我们可以试试代码了。基本上,它与@eyllanesc 的答案中的解决方案相同,除了:
在我们做新的“子类”布局之前,必须删除 QtDesigner 中添加到 QWidget 的布局和滑块 但是,当小部件子类的__init__
运行时,它不还知道它有来自 QtDesigner 的子类(布局和滑块)
因此,init 必须延迟运行,因此我们可以先从 QtDesigner 中删除子项(布局和滑块) - 然后我们可以填充“子类”子项(新布局、滑块和行编辑)
然而,在 PyQt5 中我们通常只有 deleteLater - 但是如果我们从 QtDesigner 中删除布局,那么我们会得到 "QLayout: Attempting to add QLayout "" to CustomWidget "widget_2",它已经有一个布局" 当我们尝试添加新的子类布局时;所以,我们必须使用sip
模块来删除“现在”的布局
考虑到所有这些,.py 和 .ui 代码如下所示,其结果如下所示:
...这对我来说基本上已经足够了,因为最终的 GUI 与我在 QtDesigner 中看到的 GUI 之间有很好的相似之处。
test2.py
import sys
from PyQt5 import QtCore, QtWidgets, QtGui, uic
import sip
class CustomWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.printInfo("__init__:")
QtCore.QTimer.singleShot(10, self.delayedInit) # 10 ms later
def printInfo(self, label=None):
if label is None:
label = "No-label"
print(label, self.layout(), self.children())
# above may print:
# __init__: None []
# delayedInit: <PyQt5.QtWidgets.QHBoxLayout object at 0x000000000667eb80> [<PyQt5.QtWidgets.QHBoxLayout object at 0x000000000667eb80>, <PyQt5.QtWidgets.QSlider object at 0x000000000667ec10>]
self.dumpObjectTree() # auto-prints to stdout
self.dumpObjectInfo() # auto-prints to stdout
def delayedInit(self):
self.printInfo("delayedInit:")
# delete contents of the pre-existing mock-up widget from QtDesigner
for tchild in reversed(self.children()):
#~ #tchild.setParent(None) # segfault
#if type(tchild) is not QtWidgets.QHBoxLayout: # works, but better compare with self.layout():
if tchild is not self.layout():
tchild.deleteLater()
else:
print("Not deletingLater", tchild)
# delete the layout of the pre-existing mock-up widget from QtDesigner;
# must be "now" (via sip), not "later", else: "QLayout: Attempting to add QLayout "" to CustomWidget "widget_2", which already has a layout"
if self.layout() is not None:
sip.delete(self.layout())
self.slider = QtWidgets.QSlider(orientation=QtCore.Qt.Vertical)
self.lineedit = QtWidgets.QLineEdit(text="aa")
# self.lineedit.setMaximumWidth(50)
hlay = QtWidgets.QHBoxLayout(self)
vlay = QtWidgets.QVBoxLayout()
vlay.addWidget(self.slider, 0, QtCore.Qt.AlignHCenter)
vlay.addWidget(self.lineedit, 0, QtCore.Qt.AlignHCenter)
hlay.addLayout(vlay)
spacer_item = QtWidgets.QSpacerItem(
40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
)
hlay.addItem(spacer_item)
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi("test2.ui", self)
self.show()
def main():
app = QtWidgets.QApplication(sys.argv)
window = MyMainWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
test2.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>436</width>
<height>354</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Want this:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item alignment="Qt::AlignHCenter">
<widget class="QSlider" name="verticalSlider">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QLineEdit" name="lineEdit">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>... by promoting these QSliders in QtDesigner:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="CustomWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QSlider" name="verticalSlider_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="CustomWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QSlider" name="verticalSlider_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>436</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>CustomWidget</class>
<extends>QWidget</extends>
<header>test2</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
运行test2.py
时的终端打印输出:
$ python3 test2.py
__init__: None []
CustomWidget::
OBJECT CustomWidget::unnamed
SIGNALS OUT
<None>
SIGNALS IN
<None>
__init__: None []
CustomWidget::
OBJECT CustomWidget::unnamed
SIGNALS OUT
<None>
SIGNALS IN
<None>
delayedInit: <PyQt5.QtWidgets.QHBoxLayout object at 0x000000000668cdc0> [<PyQt5.QtWidgets.QHBoxLayout object at 0x000000000668cdc0>, <PyQt5.QtWidgets.QSlider object at 0x000000000668ce50>]
CustomWidget::widget
QHBoxLayout::horizontalLayout_4
QSlider::verticalSlider_3
OBJECT CustomWidget::widget
SIGNALS OUT
signal: destroyed(QObject*)
<functor or function pointer>
SIGNALS IN
<None>
Not deletingLater <PyQt5.QtWidgets.QHBoxLayout object at 0x000000000668cdc0>
delayedInit: <PyQt5.QtWidgets.QHBoxLayout object at 0x000000000668c940> [<PyQt5.QtWidgets.QHBoxLayout object at 0x000000000668c940>, <PyQt5.QtWidgets.QSlider object at 0x000000000668cca0>]
CustomWidget::widget_2
QHBoxLayout::horizontalLayout_3
QSlider::verticalSlider_2
OBJECT CustomWidget::widget_2
SIGNALS OUT
signal: destroyed(QObject*)
<functor or function pointer>
SIGNALS IN
<None>
Not deletingLater <PyQt5.QtWidgets.QHBoxLayout object at 0x000000000668c940>
【讨论】:
以上是关于PyQt5:添加lineEdit的子类QSlider(用于在QtDesigner中提升)?的主要内容,如果未能解决你的问题,请参考以下文章
将数据从散点图传输到 lineEdit - Matplotlib 和 Pyqt5