【问题标题】:How To implementing multi layer frame or widget in Qt?如何在 Qt 中实现多层框架或小部件?
【发布时间】:2021-04-27 14:06:40
【问题描述】:

如何在 Qt 中实现这一点,我正在使用 PyQt 和 Qt Designer,但对如何使用分层感到困惑,如下图所示。

  • 我想在索引 0 的第一层显示图像或视频,
  • 那么索引 2 中的第二层是媒体控件,当鼠标没有移动时可以隐藏,
  • 在示例中更改音量时索引 3 中的第 3 层
  • 索引 4 中的第 4 层用于通知等。

在 Qt 中可以做到这一点吗?

【问题讨论】:

  • 当我在 qt4/c++ 上工作时,我通常在一个小部件(Qlabel 是通常的容器)上设置布局,然后将子小部件(例如按钮)设置到这个布局上。不知道这是如何在 qt5 和 qml 中完成的......
  • @Scheff 对于这种界面,单独覆盖paintEvent不会有效:至少第2层和第3层需要鼠标交互(第三层可能能够在界面周围移动),并且进行所有对象几何所需的所有计算可能会导致性能下降。使用没有任何布局的“浮动”子小部件是更好的解决方案。
  • @musicamante 我同意。我没想到通过将小部件彼此堆叠在一起可以更简单地实现效果。 QLayout(就像@user3528438 推荐的一样)。
  • 如果 layer1 是视频,那么这可能会很困难。 Qt 中的视频渲染通常交给第三方库,例如 gstreamer。因此,Qt 无法控制视频的渲染,因此不能充当各个层的合成器。
  • @G.M.可以使用 QGraphicsVideoItem 和使用 QGraphicsView 而不是 QVideoWidget 来解决。

标签: qt pyqt5 qt-designer pyside2


【解决方案1】:

有一种实现“层”的简单方法 - 您可以在没有任何布局的情况下添加子小部件,并在主机小部件的调整大小事件(使用事件过滤器)上调整大小/移动它们。第一层可以像往常一样以任何布局进行组织。

from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt

class Layer(QtCore.QObject):
    def __init__(self, host, child, alignment = Qt.AlignLeft, setWidth = False, setHeight = False, parent = None):
        super().__init__(parent)
        self._host = host
        self._child = child
        self._alignment = alignment
        self._setWidth = setWidth
        self._setHeight = setHeight
        child.setParent(host)
        host.installEventFilter(self)

    def eventFilter(self, watched, event):
        if watched != self._host or event.type() != QtCore.QEvent.Resize:
            return False
        hostSize = event.size()
        childSize = self._child.sizeHint()
        alignment = self._alignment
        x = 0
        y = 0
        dWidth = max(0, hostSize.width() - childSize.width())
        dHeight = max(0, hostSize.height() - childSize.height())
        if alignment & Qt.AlignRight:
            x = dWidth
        elif alignment & Qt.AlignHCenter:
            x = dWidth / 2
        if alignment & Qt.AlignVCenter:
            y = dHeight / 2
        elif alignment & Qt.AlignBottom:
            y = dHeight
        width = hostSize.width() if self._setWidth else childSize.width()
        height = hostSize.height() if self._setHeight else childSize.height()
        self._child.setGeometry(x, y, width, height)
        return False

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    widget = QtWidgets.QWidget()
    label1 = QtWidgets.QLabel("right label")
    label2 = QtWidgets.QLabel("bottom label")
    layer1 = Layer(widget, label1, Qt.AlignRight)
    layer2 = Layer(widget, label2, Qt.AlignBottom | Qt.AlignHCenter, True)
    widget.show()
    sys.exit(app.exec_())

【讨论】:

    【解决方案2】:

    这是极少数建议使用布局的情况之一,因为可见小部件是“浮动的”,应该可以四处移动。

    解决方案是创建一个“容器”小部件,将所有这些控件作为子控件

    然后,一些小部件将需要重新定位和调整大小(例如,控制栏应始终位于底部并占据整个宽度),这可以在resizeEvent() 中实现。

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class ControlBar(QtWidgets.QFrame):
        def __init__(self, parent):
            super().__init__(parent)
            layout = QtWidgets.QVBoxLayout(self)
            self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
            layout.addWidget(self.slider)
            buttons = QtWidgets.QHBoxLayout()
            layout.addLayout(buttons)
            buttons.addWidget(QtWidgets.QToolButton(text='play'))
            buttons.addWidget(QtWidgets.QToolButton(text='stop'))
            buttons.addStretch()
    
    
    class VolumeWidget(QtWidgets.QFrame):
        def __init__(self, parent):
            super().__init__(parent)
            layout = QtWidgets.QVBoxLayout(self)
            layout.setContentsMargins(2, 2, 2, 0)
            layout.setSpacing(1)
            handle = QtWidgets.QFrame()
            handle.setFixedHeight(12)
            handle.setStyleSheet('''
                QFrame {
                    border: 1px solid darkGray;
                    border-radius: 2px;
                    background: #aa646464;
                }
            ''')
            layout.addWidget(handle)
            volumeLayout = QtWidgets.QHBoxLayout()
            layout.addLayout(volumeLayout)
            for i in range(4):
                volumeLayout.addWidget(QtWidgets.QSlider(QtCore.Qt.Vertical))
    
        def mousePressEvent(self, event):
            if event.button() == QtCore.Qt.LeftButton:
                self.startPos = event.pos()
    
        def mouseMoveEvent(self, event):
            if event.buttons() == QtCore.Qt.LeftButton:
                delta = event.pos() - self.startPos
                self.move(self.pos() + delta)
    
    
    class Notification(QtWidgets.QFrame):
        def __init__(self, parent):
            super().__init__(parent)
            layout = QtWidgets.QHBoxLayout(self)
            self.label = QtWidgets.QLabel('Notification', alignment=QtCore.Qt.AlignCenter)
            layout.addWidget(self.label)
    
    
    class PlayerWidget(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            self.video = QtWidgets.QLabel(self)
            self.video.setPixmap(QtGui.QPixmap('movie.png'))
            self.video.setScaledContents(True)
            self.controlBar = ControlBar(self)
            self.notification = Notification(self)
            self.volumeWidget = VolumeWidget(self)
            self.volumeWidget.move(30, 30)
    
            self.setStyleSheet('''
                VolumeWidget, ControlBar {
                    border: 1px outset darkGray;
                    border-radius: 4px;
                    background: #aad3d3d3;
                }
                VolumeWidget:hover, ControlBar:hover {
                    background: #d3d3d3;
                }
                Notification {
                    border: 1px outset darkGray;
                    border-radius: 4px;
                    background: #aa242424;
                }
                Notification QLabel {
                    color: white;
                }
            ''')
    
        def sizeHint(self):
            if self.video.pixmap() and not self.video.pixmap().isNull():
                return self.video.pixmap().size()
            return QtCore.QSize(640, 480)
    
        def resizeEvent(self, event):
            # set the geometry of the "video"
            videoRect = QtCore.QRect(
                QtCore.QPoint(), 
                self.video.sizeHint().scaled(self.size(), QtCore.Qt.KeepAspectRatio))
            videoRect.moveCenter(self.rect().center())
            self.video.setGeometry(videoRect)
    
            # control panel
            controlHeight = self.controlBar.sizeHint().height()
            controlRect = QtCore.QRect(0, self.height() - controlHeight, 
                self.width(), controlHeight)
            self.controlBar.setGeometry(controlRect)
    
            # notification
            notificationWidth = max(self.notification.sizeHint().width(), self.width() * .6)
            notificationRect = QtCore.QRect(
                (self.width() - notificationWidth) * .5, 20, 
                notificationWidth, self.notification.sizeHint().height()
            )
            self.notification.setGeometry(notificationRect)
    
        def paintEvent(self, event):
            qp = QtGui.QPainter(self)
            qp.fillRect(self.rect(), QtCore.Qt.black)
    

    请注意,在这个简单的例子中,我只使用了一张图片。如果你想播放视频,你应该使用 Qt Multimedia 模块,并且为了正确地获得控件的透明度,必须使用带有 QGraphicsVideoItem 的 QGraphicsView。
    在这种情况下,PlayerWidget 可以直接是 QGraphicsView 的子类。

    【讨论】:

    • 表示Qwidget(PlayerWidgets)中的视频或图像位置?不需要在第一层创建一个帧,然后在第二层创建另一个帧进行视频控制?
    • 如果在使用 html 和 css 的 web 中,我们可以使用 div 创建一个图层并填充内容,然后创建另一个 div 并更改 css 中的索引以使第 1 层和其他图层堆叠在一起.我们可以在 Qt 中实现这个概念吗?
    • @blanksix 抱歉,我无法理解您的第一条评论,您能尝试更好地解释一下自己吗?关于第二条评论,从概念上讲,这正是我所做的。你试过我的代码吗?你读过它的类的文档吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-15
    • 2010-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多