Как поместить один виджет поверх другого?


В главный контейнер main_box попадают кнопка btn и цветной контейнер box.

В box я добавляю четыре виджета:

  1. self.back_picture - изображение, которое располагается в верхней части контейнера, полученное с помощью класса BackPicture. Кнопка btn, привязанная к методу remake класса MyWindow, меняет картинку в self.back_picture. Это очень важно, так как говорит о непостоянности содержимого self.back_picture.
  2. self.round_picture - изображение с закругленными краями, полученное с помощью класса RoundPicture. Подобно self.back_picture, картинка в self.round_picture будет в дальнейшем меняться. Я не стал добавлять эту возможность сейчас, чтобы не загромождать код.
  3. Два виджета QLabel - label1 и label2, которые не выполняют никаких прямых функций, но нужны для понимания того, как контейнер box располагает внутри себя виджеты.

Окно приложения:

введите сюда описание изображения

Я нарисовал схему того, что мне теперь нужно(ниже будет описание):

введите сюда описание изображения

  1. self.round_picture располагается над self.back_picture.
  2. Расстояние между левой границей box и self.round_picture равно расстоянию между self.round_picture и правой границей box. Эти расстояния постоянны.
  3. self.back_picture остается на своем месте - в самом верху контейнера box1. Между верхней границей box и виджетом нет абсолютно никакого расстояния. Так должно быть при любом размере окна.
  4. Между self.round_picture и верхней границей box1 добавляется растяжимое пространство stretch = 3.
  5. Между self.round_picture и label1 добавляется растяжимое пространство stretch = 3.
  6. Между label1 и label2 добавляется растяжимое пространство stretch = 1.
  7. Между label2 и нижней границей box1 добавляется растяжимое пространство stretch = 3.
  8. Возможность менять картинки на self.round_picture и self.back_picture должна сохраниться.

Фон виджетам label1 и label2 на схеме я добавил, чтобы было лучше видно. В реальности фона нет, но он и не нужен.

Пожалуйста, помогите мне расположить виджеты так, как я описал

from PyQt5 import QtCore, QtWidgets, QtGui

class RoundPicture(QtWidgets.QLabel):
    clicked = QtCore.pyqtSignal()

    def __init__(self, picture, x, *args, **kwargs):
        super(RoundPicture, self).__init__(*args, **kwargs)

        self.setFixedSize(x, x)
        self.x = x
        self.radius = 15 

        self.setPicture(picture)

    def setPicture(self, picture):
        target = QtGui.QPixmap(self.size())
        target.fill(QtCore.Qt.transparent)

        p = QtGui.QPixmap(picture).scaled(
            self.x, self.x, QtCore.Qt.KeepAspectRatioByExpanding,
            QtCore.Qt.SmoothTransformation
        )

        painter = QtGui.QPainter(target)
        painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
        painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
        painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)

        path = QtGui.QPainterPath()
        path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)
        painter.setClipPath(path)
        painter.drawPixmap(0, 0, p)
        self.setPixmap(target)

        painter.end()
        target = None

    def mouseReleaseEvent(self, event):
        self.clicked.emit()


class BackPicture(QtWidgets.QLabel):
    def __init__(self, picture, x, *args, **kwargs):
        super(BackPicture, self).__init__(*args, **kwargs)

        self.setFixedSize(x, x)
        self.x = x

        self.setPicture(picture)

    def setPicture(self, picture):
        self.setPixmap(QtGui.QPixmap(picture).scaled(self.x, self.x, QtCore.Qt.KeepAspectRatio))


class MyWindow(QtWidgets.QWidget):
    def __init__(self, parent = None):
        super().__init__(parent)

        self.index = 1

        main_box = QtWidgets.QHBoxLayout(self)

        btn = QtWidgets.QPushButton('Remake', clicked = self.remake)
        main_box.addWidget(btn)

        container = QtWidgets.QWidget()
        container.setStyleSheet('background: #2A303D;')
        container.setMinimumHeight(300)
        container.setFixedWidth(300)

        main_box.addWidget(container)
        box = QtWidgets.QVBoxLayout(container)
        box.setContentsMargins(0, 0, 0, 0)

        self.back_picture = BackPicture('picture3.jpg', 300)       
        box.addWidget(self.back_picture, alignment = QtCore.Qt.AlignCenter)

        self.round_picture = RoundPicture('picture1.png', 150)
        self.round_picture.setStyleSheet('background: transparent;')
        box.addWidget(self.round_picture, alignment = QtCore.Qt.AlignCenter)

        label1 = QtWidgets.QLabel('Text number 1')
        label1.setStyleSheet(qss)
        box.addWidget(label1, alignment = QtCore.Qt.AlignCenter)

        label2 = QtWidgets.QLabel('Text number 2')
        label2.setStyleSheet(qss)
        box.addWidget(label2, alignment = QtCore.Qt.AlignCenter)

        box.setStretch(0, 0)
        box.setStretch(1, 2)
        box.setStretch(2, 2)

    def remake(self):
        if self.index == 1:
            self.back_picture.setPicture('picture2.png')
            self.index = 2
        else:
            self.back_picture.setPicture('picture3.jpg')
            self.index = 1


qss = '''QLabel {
             color: red;
             font: bold 16px;
             background: transparent;
         }'''

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    window.setWindowTitle(' ')
    window.show()
    sys.exit(app.exec_())

Ответов: 2


S. Nick

Я убрал back_picture из макета. Попробуйте что получилось.

from PyQt5 import QtCore, QtWidgets, QtGui

class RoundPicture(QtWidgets.QLabel):
    clicked = QtCore.pyqtSignal()

    def __init__(self, picture, x, *args, **kwargs):
        super(RoundPicture, self).__init__(*args, **kwargs)
        self.setFixedSize(x, x)
        self.x = x
        self.radius = 15 
        self.setPicture(picture)

    def setPicture(self, picture):
        target = QtGui.QPixmap(self.size())
        target.fill(QtCore.Qt.transparent)
        p = QtGui.QPixmap(picture).scaled(
            self.x, self.x, 
            QtCore.Qt.KeepAspectRatioByExpanding,
            QtCore.Qt.SmoothTransformation
        )
        painter = QtGui.QPainter(target)
        painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
        painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
        painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
        path = QtGui.QPainterPath()
        path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)
        painter.setClipPath(path)
        painter.drawPixmap(0, 0, p)
        self.setPixmap(target)
        painter.end()
        target = None

    def mouseReleaseEvent(self, event):
        self.clicked.emit()


class BackPicture(QtWidgets.QLabel):
    def __init__(self, picture, x, *args, **kwargs):
        super(BackPicture, self).__init__(*args, **kwargs)
        self.setFixedSize(x, x)
        self.x = x
        self.setPicture(picture)

    def setPicture(self, picture):
        self.setPixmap(QtGui.QPixmap(picture).scaled(self.x, self.x, QtCore.Qt.KeepAspectRatio))


class MyWindow(QtWidgets.QWidget):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.index = 1

        btn = QtWidgets.QPushButton('Remake', clicked = self.remake)

        self.container = QtWidgets.QWidget()
        self.container.setStyleSheet('background: #2A303D;')
        self.container.setMinimumHeight(300)
        self.container.setFixedWidth(300)

        main_box = QtWidgets.QHBoxLayout(self)                                  # box1  ??? 
        main_box.addWidget(btn)        
        main_box.addWidget(self.container)

        box = QtWidgets.QVBoxLayout(self.container)
        box.setContentsMargins(0, 0, 0, 0)

        self.back_picture = BackPicture('300_300.png', 300, self.container)      # + , self.container
#        box.addWidget(self.back_picture, alignment = QtCore.Qt.AlignCenter)     # ---
        self.back_picture.move(0, 0)                                             # +++

        box.addStretch(3)                                                         # +++

        self.round_picture = RoundPicture('head2.jpg', 150)                       
        self.round_picture.setStyleSheet('background: transparent;')
        box.addWidget(self.round_picture, alignment = QtCore.Qt.AlignCenter)

        box.addStretch(3)                                                         # +++

        label1 = QtWidgets.QLabel('Text number 1')
        label1.setStyleSheet(qss)
        box.addWidget(label1, alignment = QtCore.Qt.AlignCenter)

        box.addStretch(1)                                                         # +++

        label2 = QtWidgets.QLabel('Text number 2')
        label2.setStyleSheet(qss)
        box.addWidget(label2, alignment = QtCore.Qt.AlignCenter)

        box.addStretch(3)                                                          # +++


        '''
#        box.setStretch(0, 0)
        box.setStretch(0, 3)      # ?

        box.setStretch(1, 2)
        box.setStretch(2, 2)

        box.setStretch(3, 2)       # ?
        '''

    def remake(self):
        if self.index == 1:
            self.back_picture.setPicture('background.png') 
            self.index = 2
        else:
            self.back_picture.setPicture('300_300.png') 
            self.index = 1


qss = '''QLabel {
             color: red;
             font: bold 16px;
             background: transparent;
         }'''

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    window.setWindowTitle(' ')
    window.show()
    sys.exit(app.exec_())

введите сюда описание изображения


0

Большое вам спасибо. Сейчас я понял, что деталь, которую я вчера забыл предусмотреть, совсем тут не нужна :) Мне не стоило закрывать вопрос

Alexander Chernin

В режиме редактирования файла ресурсов и ui-файла (QtCreator)

  1. Добавляете картинку в файл ресурсов проекта (файл qrs).
  2. В режиме редактирования ui-файла выбираете любой виджет и в панеле настроек находите и нажимаете (на кнопку с тремя точками) в пункте styleSheet
  3. Там, в выпадающем меню "Добавить ресурс", выбираете background-image
  4. Слева выбираете "<Resource Root>" - справа появятся соответствующие картинки из файла ресурсов. Выбираете необходимую картинку.
  5. В поле qss появится следующая запись background-image: url(...); которую удаляете, предварительно скопировав url(...)
  6. Далее вводите следующую строку
#<имя переменной вашего виджета> {
    border-image: url(:</path/to/the/picture.ext>) 0 0 0 0 stretch stretch;
}

таким образом вы заполните вашей картинкой весь фон конкретного виджета. Для пропорционального заполнения - поиграйтесь с параметрами border-image, или размерами самого виджета

NB. Без задания имени переменной виджета #<имя переменной вашего виджета> картинка станет фоном у всех дочерних виджетов данного виджета


0

Большое спасибо. К сожалению, я не могу отметить второй ответ правильным :(

0

@MAXIM045 все нормально, я видел, что уже есть правильный ответ, просто описал альтернативный вариант. Можете подарить мне за труды 10 очков, нажав на стрелочку вверх :))

0

Я уже давно нажал на стрелочку :)

0

@MAXIM045 Благодарю!

задан
1 неделю 3 дня назад
просмотрен
15
обновлён
1 неделю 3 дня назад