简体   繁体   English

如何在PyQt5中添加一条隐形线

[英]How to add an invisible line in PyQt5

在此处输入图像描述

This attached image is the screenshot of an application developed using PyQt5. The image clearly has an invisible line running in the middle of the boxes enclosing the contents.这张附件图片是使用 PyQt5 开发的应用程序的屏幕截图。图像中明显有一条看不见的线在包围内容的方框中间运行。 What code should I add in my program to draw an invisible line overlaying all other objects created earlier.我应该在我的程序中添加什么代码来绘制一条不可见的线来覆盖之前创建的所有其他对象。 I couldn't find any documentation regarding this but as the image suggests, it has somehow been implemented.我找不到与此相关的任何文档,但正如图片所示,它已经以某种方式实现了。

A code snippet is not needed to be provided by me since this is a question about adding/developing a feature rather than debugging or changing any existing code.我不需要提供代码片段,因为这是一个关于添加/开发功能的问题,而不是调试或更改任何现有代码。

Premise: what you provided as an example doesn't seem a very good thing to do.前提:您提供的示例似乎不是一件好事。 It also seems more a glich than a "feature", and adding "invisible" lines like that might result in an annoying GUI for the user.它看起来更像一个小故障而不是一个“功能”,并且添加像这样的“不可见”行可能会导致用户讨厌的 GUI。 The only scenario in which I'd use it would be a purely graphical/fancy one, for which you actually want to create a "glitch" for some reason.我会使用它的唯一场景是纯粹的图形/奇特场景,出于某种原因你实际上想要创建一个“故障”。 Also, note that the following solutions are not easy, and their usage requires you an advanced skill level and experience with Qt, because if you don't really understand what's happening, you'll most certainly encounter bugs or unexpected results that will be very difficult to fix.另外,请注意以下解决方案并不容易,使用它们需要您具有高级技能水平和使用 Qt 的经验,因为如果您不真正了解发生了什么,您肯定会遇到错误或意想不到的结果,这将是非常很难修复。

Now.现在。 You can't actually "paint an invisible line", but there are certain work arounds that can get you a similar result, depending on the situation.您实际上无法“画出一条看不见的线”,但根据具体情况,有一些变通方法可以获得类似的结果。

The main problem is that painting (at least on Qt) happens from the "bottom" of each widget, and each child widget is painted over the previous painting process, in reverse stacking order: if you have widgets that overlap, the topmost one will paint over the other.主要问题是绘画(至少在 Qt 上)是从每个小部件的“底部”开始的,并且每个子小部件都是在前一个绘画过程中以相反的堆叠顺序绘制的:如果你有重叠的小部件,最上面的会在另一个上涂漆。 This is more clear if you have a container widget (such as a QFrame or a QGroupBox) with a background color and its children use another one: the background of the children will be painted over the parent's.如果您有一个带有背景色的容器小部件(例如 QFrame 或 QGroupBox)并且其子级使用另一种颜色,则这一点会更清楚:子级的背景将被绘制父级的背景之上。

The (theoretically) most simple solution is to have a child widget that is not added to the main widget layout manager. (理论上)最简单的解决方案是拥有一个添加到主小部件布局管理器的子小部件。

Two important notes:两个重要说明:

  1. The following will only work if applied to the topmost widget on which the "invisible line" must be applied.以下仅适用于必须应用“隐形线”的最顶层小部件。
  2. If the widget on which you apply this is not the top level window, the line will probably not be really invisible.如果您应用它的小部件不是顶层 window,则该行可能不会真正不可见。
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)

Unfortunately, this approach has a big issue: if the background color has an alpha component or uses a pixmap (like many styles do, and you have NO control nor access to it), the result will not be an invisible line.不幸的是,这种方法有一个问题:如果背景颜色有一个 alpha 分量或使用一个像素图(就像许多styles那样,并且您无法控制或访问它),结果将不会是一条不可见的线。

Here is a screenshot taken using the "Oxygen" style (I set a 20 pixel spacing for the layout);这是使用“氧气”样式截取的屏幕截图(我为布局设置了 20 像素的间距); as you can see, the Oxygen style draws a custom gradient for window backgrounds, which will result in a "not invisible line":如您所见,Oxygen 样式为 window 背景绘制自定义渐变,这将导致“不可见线”:

不是真正的隐形线

The only easy workaround for that is to set the background using stylesheets (changing the palette is not enough, as the style will still use its own way of painting using a gradient derived from the QPalette.Window role):唯一简单的解决方法是使用样式表设置背景(更改调色板是不够的,因为样式仍将使用其自己的绘画方式,使用从 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;
            }
        ''')

The selectors are required to avoid stylesheet propagation to child widgets;选择器需要避免样式表传播到子部件; I used the '#' selector to identify the object name of the "invisible" widget.我使用“#”选择器来识别“不可见”小部件的名称 object。

As you can see, now we've lost the gradient, but the result works as expected:正如您所看到的,现在我们已经失去了渐变,但结果如预期的那样工作:

现在效果更好

Now.现在。 There's another, more complicated solution, but that should work with any situation, assuming that you're still using it on a top level window.还有另一个更复杂的解决方案,但它应该适用于任何情况,假设您仍在顶级 window 上使用它。

This approach still uses the child widget technique, but uses QWidget.render() to paint the current background of the top level window on a QPixmap, and then set that pixmap to the child widget (which now is a QLabel).这种方法仍然使用子部件技术,但使用QWidget.render()在 QPixmap 上绘制顶层 window 的当前背景,然后将该像素图设置为子部件(现在是 QLabel)。
The trick is to use the DrawWindowBackground render flag , which allows us to paint the widget without any children.诀窍是使用DrawWindowBackground渲染标志,它允许我们绘制没有任何孩子的小部件。 Note that in this case I used a black background, which shows a "lighter" gradient on the borders that better demonstrate the effect:请注意,在这种情况下,我使用了黑色背景,它在边框上显示了一个“较浅”的渐变,可以更好地展示效果:

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())

And here is the result:结果如下:

现在这看起来真的很好

Finally, an further alternative would be to manually apply a mask to each child widget, according to their position. But that could become really difficult (and possibly hard to manage/debug) if you have complex layouts or a high child count, since you'd need to set (or unset) the mask for all direct children each time a resize event occurs.最后,另一种选择是根据他们的 position 手动为每个子部件应用掩码。但是如果您有复杂的布局或大量子部件,这可能会变得非常困难(并且可能难以管理/调试),因为您每次发生调整大小事件时,都需要为所有直接子级设置(或取消设置)掩码。 I won't demonstrate this scenario, as I believe it's too complex and unnecessary.我不会演示这种情况,因为我认为它太复杂且没有必要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM