Как добавить невидимую строку в PyQt5 - PullRequest
0 голосов
/ 15 апреля 2020

enter image description here

Прикрепленное изображение представляет собой скриншот приложения, разработанного с использованием PyQt5. На изображении четко видна невидимая линия, проходящая посередине ящиков с содержимым. Какой код я должен добавить в моей программе, чтобы нарисовать невидимую линию, перекрывающую все другие объекты, созданные ранее. Я не смог найти никакой документации по этому вопросу, но, как видно из рисунка, он каким-то образом реализован.

Мне не нужно предоставлять фрагмент кода, так как это скорее вопрос добавления / разработки функции чем отладка или изменение любого существующего кода.

1 Ответ

1 голос
/ 15 апреля 2020

Предпосылка: то, что вы привели в качестве примера, кажется не очень хорошей вещью. Это также кажется скорее глюком, чем «функцией», и добавление таких «невидимых» строк может привести к раздражающему GUI для пользователя. Единственный сценарий, в котором я бы использовал его, был бы чисто графическим / причудливым, для которого вы действительно хотите создать «сбой» по некоторым причинам. Также обратите внимание, что следующие решения не просты, и их использование требует от вас продвинутого уровня навыков и опыта работы с Qt, потому что, если вы действительно не понимаете, что происходит, вы наверняка столкнетесь с ошибками или неожиданные результаты, которые будет очень трудно исправить.

Сейчас. На самом деле вы не можете «нарисовать невидимую линию», но есть определенные обходные пути, которые могут дать вам аналогичный результат, в зависимости от ситуации.

Основная проблема в том, что происходит рисование (по крайней мере, на Qt) от «низа» каждого виджета, и каждый дочерний виджет закрашивается поверх предыдущего процесса рисования в обратном порядке расположения: если у вас есть виджеты, которые перекрываются, самый верхний закрашивает другой. Это более ясно, если у вас есть контейнерный виджет (например, QFrame или QGroupBox) с цветом фона, а его дочерние элементы используют другой: фон дочерних элементов будет закрашен поверх родительского.

(Теоретически) самое простое решение - добавить дочерний виджет, который не , добавлен в главный менеджер компоновки виджетов.

Два важных примечания:

  1. Следующее будет работать только в случае применения к верхнему виджету, к которому должна быть применена «невидимая линия».
  2. Если виджет, к которому вы применяете это , а не верхний уровень окно, линия, вероятно, будет не действительно невидимой.
class TestWithChildLine(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QGridLayout(self)
        for row in range(3):
            for col in range(6):
                layout.addWidget(QtWidgets.QDial(), row, col)

        # create a widget child of this one, but *do not add* it to the layout
        self.invisibleWidget = QtWidgets.QWidget(self)
        # ensure that the widget background is painted
        self.invisibleWidget.setAutoFillBackground(True)
        # and that it doesn't receive mouse events
        self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        # create a rectangle that will be used for the "invisible" line, wide
        # as the main widget but with 10 pixel height, then center it
        rect = QtCore.QRect(0, 0, self.width(), 10)
        rect.moveCenter(self.rect().center())
        # set the geometry of the "invisible" widget to that rectangle
        self.invisibleWidget.setGeometry(rect)

К сожалению, у этого подхода есть большая проблема: если цвет фона имеет альфа-компонент или использует растровое изображение (как это делают многие стили, и у вас есть NO элемент управления или доступ к нему), в результате не будет невидимой строкой.

Вот скриншот, сделанный с помощью стиль «Кислород» (для макета я установил интервал в 20 пикселей); как вы можете видеть, в стиле Oxygen dr aws пользовательский градиент для оконных фонов, который приведет к появлению «невидимой линии»:

Not really an invisible line

Единственный простой способ для этого - установить фон с помощью таблиц стилей (изменение палитры недостаточно, поскольку стиль все равно будет использовать свой собственный способ рисования с использованием градиента, полученного из роли QPalette.Window):

        self.invisibleWidget = QtWidgets.QWidget(self)
        self.invisibleWidget.setObjectName('InvisibleLine')
        self.invisibleWidget.setAutoFillBackground(True)
        self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
        self.setStyleSheet('''
            TestWithChildFull, #InvisibleLine {
                background: lightGray;
            }
        ''')

Селекторы необходимы, чтобы избежать распространения таблицы стилей на дочерние виджеты; Я использовал селектор «#» для определения имени объекта «невидимого» виджета.

Как вы можете видеть, теперь мы потеряли градиент, но результат работает, как и ожидалось:

now that works better

Сейчас. Есть и другое, более сложное решение, но оно должно работать в любой ситуации, при условии, что вы все еще используете его в окне верхнего уровня.

Этот подход все еще использует технику дочерних виджетов, но использует QWidget.render() для рисования текущего фона окна верхнего уровня на QPixmap, а затем установите это растровое изображение на дочерний виджет (который теперь является QLabel).
Хитрость заключается в использовании DrawWindowBackground render flag , что позволяет рисовать виджет без дочерних элементов. Обратите внимание, что в этом случае я использовал черный фон, который показывает «более светлый» градиент на границах, которые лучше демонстрируют эффект:

class TestWithChildLabel(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QGridLayout(self)
        layout.setSpacing(40)
        for row in range(3):
            for col in range(6):
                layout.addWidget(QtWidgets.QDial(), row, col)

        self.invisibleWidget = QtWidgets.QLabel(self)
        self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)

        palette = self.palette()
        palette.setColor(palette.Window, QtGui.QColor('black'))
        self.setPalette(palette)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        pm = QtGui.QPixmap(self.size())
        pm.fill(QtCore.Qt.transparent)
        qp = QtGui.QPainter(pm)
        maskRect = QtCore.QRect(0, 0, self.width(), 50)
        maskRect.moveTop(50)
        region = QtGui.QRegion(maskRect)
        self.render(qp, maskRect.topLeft(), flags=self.DrawWindowBackground,
            sourceRegion=region)
        qp.end()
        self.invisibleWidget.setPixmap(pm)
        self.invisibleWidget.setGeometry(self.rect())

И вот результат:

now this seems really fine

Наконец, еще одна альтернатива - вручную применить маску к каждому дочернему виджету в соответствии с их положением. Но это может стать очень трудным (и, возможно, трудным для управления / отладки), если у вас сложные макеты или большое количество дочерних элементов, так как вам нужно будет установить (или отменить) маску для всех прямых дочерних элементов каждый время, когда происходит событие изменения размера. Я не буду демонстрировать этот сценарий, так как считаю, что он слишком сложный и ненужный.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...