How to make Qtooltip corner round in pyqt5

469 Views Asked by At

How to make Qtooltip corner round in pyqt5. Tried many stylesheet but it does not work. The frame on Qtooltip remains same.

1

There are 1 best solutions below

2
On

Qt Style Sheets cannot set the mask of a widget, which is what is required to change the shape of top level widgets to a different one than standard rectangles.

While this question has a partial answer, it's quite old and doesn't explain how to properly use the style hint and implement the style.

The solution is based on a QProxyStyle that updates the returnData of styleHint() by creating a proper mask based on the contents.

The biggest issue here is that there is absolutely no way to know the QSS properties used by a widget (and accessing the widget's styleSheet() is pointless, since style sheets can be inherited): we can never know if a widget has a border, and the possible radius of that border.

There is a way, though: we could locally render the tooltip on a QImage, and use createHeuristicMask() to get a possible mask based on the contents, which should be set using the application stylesheets.

If, for some reason, the mask is still the full rectangle of the widget, we can "guess" a standard rounded mask anyway.

Note that the returnData of styleHint() is a QStyleHintReturn, while we need a QStyleHintReturnMask because we must be able to write its region; in order to achieve this, we use the cast() function of the sip module (usually installed along with PyQt).

The following uses the default palette colors for the tooltip style sheet, but the important aspect is that the border must be slightly different than the background (otherwise the above mask creation won't work).

from PyQt5 import QtCore, QtGui, QtWidgets
import sip

class ProxyStyle(QtWidgets.QProxyStyle):
    def styleHint(self, hint, opt=None, widget=None, returnData=None):
        if hint == self.SH_ToolTip_Mask and widget:
            if super().styleHint(hint, opt, widget, returnData):
                # the style already creates a mask
                return True
            returnData = sip.cast(returnData, QtWidgets.QStyleHintReturnMask)
            src = QtGui.QImage(widget.size(), QtGui.QImage.Format_ARGB32)
            src.fill(QtCore.Qt.transparent)
            widget.render(src)

            mask = QtGui.QRegion(QtGui.QBitmap.fromImage(
                src.createHeuristicMask()))
            if mask == QtGui.QRegion(opt.rect.adjusted(1, 1, -1, -1)):
                # if the stylesheet doesn't set a border radius, create one
                x, y, w, h = opt.rect.getRect()
                mask = QtGui.QRegion(x + 4, y, w - 8, h)
                mask += QtGui.QRegion(x, y + 4, w, h - 8)
                mask += QtGui.QRegion(x + 2, y + 1, w - 4, h - 2)
                mask += QtGui.QRegion(x + 1, y + 2, w - 2, h - 4)

            returnData.region = mask
            return 1
        return super().styleHint(hint, opt, widget, returnData)


app = QtWidgets.QApplication([])
app.setStyle(ProxyStyle())
palette = app.palette()
app.setStyleSheet('''
    QToolTip {{
        color: {fgd};
        background: {bgd};
        border: 1px solid {border};
        border-radius: 4px;
    }}
'''.format(
    fgd=palette.color(palette.ToolTipText).name(), 
    bgd=palette.color(palette.ToolTipBase).name(), 
    border=palette.color(palette.ToolTipBase).darker(110).name()
))
test = QtWidgets.QPushButton('Hover me', toolTip='Tool tip')
test.show()
app.exec()

Be aware, though, that this is not very optimal, because it involves rendering the tooltip every time it's being shown/resized: if your program shows many tooltips or updates them very frequently, performance can become poor. The alternative is to completely skip the heuristic mask creation and just set a predefined and default mask based on the contents, as shown near the end of the styleHint() override above.