0

For my text editor I'm trying to implement a QMenu to quickly create a table with a size of up to 10 columns and 8 rows (lines). It looks and works similar to what you have in Word or Google Docs like this: (https://i.stack.imgur.com/IxbPf.png)

In my application it looks like this: (https://i.stack.imgur.com/ciH7o.png)

You have a QGridLayout of 10x8 buttons. If you hover over a button its background colour and the button colour of all buttons with a lower column and row number will change their background colour to indicate the size of the table.

I use an enterEvent to emit a signal to the parent QWidgetAction with the grid coordinates, and every button that has the same or lower coordinates that the hovered button's coordinates have the property, "hovered" changed. In the corresponding stylesheet this property should change the background colour but that doesn't happen.

I've looked over the code several times but I really can't see the problem why the buttons' background colour doesn't change so I would appreciate if someone has an answer.

Here is the code of the QMenu, the QWidgetAction with the button grid and the special QPushButton "GridButton":

class TableMenu(QtWidgets.QMenu):
    tableSize = QtCore.pyqtSignal(int, int)

    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.setUI()

    def setUI(self) -> None:
        self.AC_TableGrid = TableGrid(self)
        self.AC_TableGrid.tableSize.connect(self.emitTableSize)
        self.addAction(self.AC_TableGrid)

    @QtCore.pyqtSlot(int, int)
    def emitTableSize(self, line: int, column: int) -> None:
        self.tableSize.emit(line, column)

class TableGrid(QtWidgets.QWidgetAction):
    tableSize = QtCore.pyqtSignal(int, int)

    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.setUI()

    def setUI(self) -> None:
        self.Grid = QtWidgets.QWidget()
        self.ButtonLayout = QtWidgets.QGridLayout(self.Grid)
        self.ButtonLayout.setHorizontalSpacing(2)
        self.ButtonLayout.setVerticalSpacing(2)

        for iline in range(8):
            for icolumn in range(10):
                button = GridButton()
                button.line = iline+1
                button.column = icolumn+1
                button.entered.connect(self.setMarkedButtons)
                button.sizeSet.connect(self.getTableSize)
                self.ButtonLayout.addWidget(button, iline, icolumn)

        self.Grid.setLayout(self.ButtonLayout)

        self.setDefaultWidget(self.Grid)

    @QtCore.pyqtSlot(int, int)
    def setMarkedButtons(self, line: int, column: int) -> None:
        for i in range(self.ButtonLayout.count()):
            button = self.ButtonLayout.itemAt(i).widget()
            if button.column <= column and button.line <= line:
                print(button, button.line, button.column)
                if button.property("hovered"):
                    button.setProperty("hovered", False)
                else:
                    button.setProperty("hovered", True)

            else:
                button.setProperty("hovered", False)
            button.style().polish(button)            

    @QtCore.pyqtSlot(int, int)
    def getTableSize(self, line: int, column: int) -> None:
        self.tableSize.emit(line, column)

class GridButton(QtWidgets.QPushButton):
    entered = QtCore.pyqtSignal(int, int)
    sizeSet = QtCore.pyqtSignal(int, int)

    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.setStyleSheet(fromStyle("GridButton")) # A convenient function to quickly load a stylesheet.
        self.setProperty("hovered", False)
        self.setFixedSize(15, 15)
        self.connectSignals()
        self.line: int
        self.column: int

    def connectSignals(self) -> None:
        self.clicked.connect(self.emitCoordinates)

    def emitCoordinates(self) -> None:
        self.sizeSet.emit(self.line, self.column)
    
    def enterEvent(self, event: QtGui.QEnterEvent) -> None:
        self.entered.emit(self.line, self.column)
        print("Entered!")
        return super().enterEvent(event)

    def leaveEvent(self, a0: QtCore.QEvent) -> None:
        # This is the property to change the background colour
        self.setProperty("hovered", False)
        self.style().polish(self)
        return super().leaveEvent(a0)

Here is the stylesheet "GridButton.qss":

QPushButton {
    background-color: white;
    border: 1px solid black;
    border-radius: 4px;
}

QPushButton [hovered='true'] {
    background-color: #75b4ec;
}

I've also tried similar functions that update the appearance like unpolish() or update() but they didn't work either.

The convenient function I created myself, fromStyle() , also works perfectly fine. I checked it. So this should not be the problem.

Deator
  • 5
  • 3
  • Typo: remove the space between `QPushButton` and the square bracket: spaces in selectors are used to separate class descendants. Also, the logic in `setMarkedButtons` seems wrong (why do you toggle the `hovered` property?). Besides, using buttons for this might not have many benefits and it may probably complicate things unnecessarily: just use a custom QWidget subclass and manually do the painting. In any case, there's no point in individually setting the qss to each button: just do it on the widget parent (`self.Grid`, which should also *not* have a capitalized name, since it's not a class). – musicamante Feb 21 '23 at 17:55
  • Thank you! It's strange that the space was the problem because I have another stylesheet where this isn't a problem. For me using buttons is the easiest solution because I don't know how to paint widgets with QPainter. Seems more complicated for me. – Deator Feb 21 '23 at 18:30
  • It probably worked because what you wrote before the brackets was considered as a parent. If you don't want to use QPainter, then use another widget: you are not really using the features QPushButton provides, so using it is pointless; use QWidget or QFrame instead. Note that using `return` in event handlers is pointless, since those functions already return `None` (any override should always do the same), and since `None` is always implicit when using a simple `return`, there is no point in that: just call the base implementation. – musicamante Feb 21 '23 at 20:14
  • Also, the mouse events should be handled by the parent, not the buttons. Right now, if the user drags the mouse on a "button" and tries to change the size before releasing the mouse button, the grid size is unchanged, which might be unexpected. – musicamante Feb 21 '23 at 20:34

1 Answers1

0

QPushButton natively have a state for hover.

QPushButton:hover{
    background-color: #000000
}
  • That is not the problem: the OP wants to manually set a property for a *range* of buttons, not only the hovered one, and the property should be used as a selector for the style sheet. The cause of the issue was a typo in the stylesheet syntax. – musicamante Feb 21 '23 at 18:01