Timing issue with PyQt6

I’ve had several issues with QDialog widget where its windows flags for the most part don’t work under ubuntu do to Wayland not fully understanding commands sent from PyQt6.

I’ve decided to abandon using the QDialog and adding extra prompts which display in the Button section I’ve designated for each page. I use a recursive function that loops through the Button sections children and deleting each when I clear it. This same section once cleared is where I display additional prompts some temporary others waiting for input.

What I’ve noticed is there’s a timing issue of some sort where the Button section hasn’t finished fully clearing its children before the new prompt displays. I’ve tried adding some additional flags to the main screen, mainscreen.repaint(), mainscreen.update() and the QTimer (oneshot enabled) to delay when the prompt show but it still doesn’t work properly.

Is there an event (paint) I can rewrite for the mainscreen to simulate a screen refresh or test if the screen has refreshed ?

Thanks in advance

weave

Hi @weave

I’ve had several issues with QDialog widget where its windows flags for the most part don’t work under ubuntu do to Wayland not fully understanding commands sent from PyQt6.

What do you want that QDialog to do?

Some code would be useful to see, what you want to achieve and where is a problem.

Behave and look exactly like most Dialogs …

  1. Be modal when shown.

  2. Show the window without a minimize maximize Buttons or at least provide customization features to hide or turn off both.

PyQt6’s Dialog does neither.

Weave

Ok, in PySide6 behaviour is diffrent on WIndows and Linux:
with this:

msg = QMessageBox(text='text', parent=self)
msg.setIcon(QMessageBox.Icon.Question)
msg.setWindowTitle('title')
msg.setInformativeText('info_txt')
msg.setDetailedText('detail_txt')
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
msg.setWindowModality(Qt.WindowModality.ApplicationModal)
msg.exec()

or

QMessageBox.question(self, "Confirm Exit", "Unsaved changes detected.")

it produce
image
with min button active

on windows is a bit better:
image
no min button.

Appreciate the input Michal but this is not a solution for me since my application will for the most part reside under ubuntu. The window manager there is Wayland and all of the window flags with the exception of the FramelessWindow is not working. To be fair X11 window manager does work properly but touch screen disappears with that setting.

I’ve complained to the PyQt6 folks to speak to the Wayland team but nothing has been done todate to correct this and it doesn’t look like it will anytime soon…

So I’m writing around my issues with the Dialog.

It’s not a timing issue at all and I’m happy to report my problem was the playsound module added to my application for warnings or prompts. There’s a block parameter that needed to be set to False and is True by default … have no idea why that would be the case !?

My recursive function which I use to clear layouts was working fine all along. So now I no longer need the QDialog widget to display prompts through out my App and can achieve this by clearing the current buttons along the bottom of my page display a temporary prompt or message which I can then after redisplay the desired by buttons (QTimer).

I’m happy to share some code if one so desires.

weave

1 Like

share a code snippet is always good idea… you never know when you need what :wink:

Recursive Clear:

Below is added as to my Main Window to allow for use through out my App.
Some Layouts (at bottom of clear) are setup as instance variables for the main window and they will persist, everything else gets deleted.

    def clear_section(self, section):

        if section == "all":
            self.current_layout = self.base_layout
            layout = self.base_layout
        else:
            self.current_layout = section
            layout = section

        # Remove all children from current layout

        for widget_no in range(0, layout.count()):

            if "Layout" in str(layout.itemAt(widget_no)):
                self.previous_layout = self.current_layout
                self.clear_section(layout.itemAt(widget_no))
            else:
                layout.itemAt(widget_no).widget().deleteLater()
                layout.update()

        # Remove any extra layouts

        if self.current_layout != self.base_layout:
            if self.current_layout != self.title_section:
                if self.current_layout != self.content_section:
                    if self.current_layout != self.side_bar_section:
                        if self.current_layout != self.buttons_section:
                            self.current_layout.deleteLater()
                            self.base_layout.update()

        # Check if this was a recursive call and if so reset the layout

        if self.previous_layout is not None:
            self.current_layout = self.previous_layout
            self.previous_layout = None

Display a prompt message in Button Section:

Again added as a Main Window method to allow for use throughout App. Is a basic function that is passed the prompt and prompt type. A Response type will display buttons and all others will display a temporary message that is cleared with a QTimer.
I’ve hard coded the ContentsMargin settings since the messages passed are preplanned but you can adjust to tighten up the display.

    def messagebox(self, message, mess_type):

        if not self.msg_prompt_shown:

            self.clear_section(self.buttons_section)

            self.response = None
            self.import_action = None

            if mess_type == "Response":
                layout1 = QHBoxLayout()
            else:
                layout1 = QHBoxLayout()

            layout2 = QGridLayout()

            icon = QLabel()

            if "Warning" in mess_type:
                image = QPixmap("./images/warn1.png").scaled(70, 70)
            elif mess_type == "Field Validation Error":
                image = QPixmap("./images/info2.png").scaled(70, 70)
            elif mess_type == "Error":
                image = QPixmap("./images/warn1.png").scaled(70, 70)
            elif mess_type == "Response":
                image = QPixmap("./images/question.png").scaled(70, 70)
                self.response = "No"
            else:
                image = QPixmap("./images/info2.png").scaled(70, 70)

            icon.setPixmap(image)
            icon.setFixedSize(70, 70)
            icon.setAlignment(Qt.AlignmentFlag.AlignCenter)

            msg = QLabel()
            msg.setText(message)
            msg.setFixedSize(500, 70)
            msg.setStyleSheet("font-size: 22px; font-weight: 500")

            layout2.addWidget(icon, 0, 0, Qt.AlignmentFlag.AlignRight)
            layout2.addWidget(msg, 0, 1, Qt.AlignmentFlag.AlignLeft)

            #layout1.addLayout(layout2)

            if mess_type == "Response":

                button = QPushButton()

                button.setObjectName("yesBtn")
                button.setFlat(True)
                button.setIcon(QIcon("./images/yes_up.png"))
                button.setFixedSize(64, 64)
                button.setIconSize(QSize(64, 64))

                button.pressed.connect(lambda btn=button: show_pressed(btn))
                button.released.connect(lambda btn=button: self.show_released(btn))

                layout2.addWidget(button, 0, 2, Qt.AlignmentFlag.AlignCenter)

                button = QPushButton()

                button.setObjectName("noBtn")
                button.setFlat(True)
                button.setIcon(QIcon("./images/no_up.png"))
                button.setFixedSize(64, 64)
                button.setIconSize(QSize(64, 64))

                button.pressed.connect(lambda btn=button: show_pressed(btn))
                button.released.connect(lambda btn=button: self.show_released(btn))

                layout2.addWidget(button, 0, 3, Qt.AlignmentFlag.AlignCenter)


            if mess_type == "Response":

                playsound("prompt.wav", False)

                layout1.addLayout(layout2)

                if len(message) < 40:
                    layout1.setContentsMargins(106, 0, 106, 0)
                else:
                    layout1.setContentsMargins(60, 0, 60, 0)

                self.buttons_section.addLayout(layout1)
                self.msg_prompt_shown = True

            else:

                if "Warning" in mess_type or mess_type == "Field Validation Error" or mess_type == "Error":
                    playsound("prompt.wav", False)

                layout1.addLayout(layout2)

                if len(message) < 35:
                    layout1.setContentsMargins(0, 0, 60, 0)
                else:
                    layout1.setContentsMargins(0, 0, 74, 0)

                self.buttons_section.addLayout(layout1)
                self.msg_prompt_shown = True

                self.timer = QTimer()

                self.timer.setSingleShot(True)
                self.timer.setInterval(2300)
                self.timer.timeout.connect(self.messagebox_action)
                self.timer.start()

    def messagebox_action(self):

        if self.response == "Yes":
            self.import_rtn()
        elif self.response == "Save Anyways":
            self.continue_with_save()
            self.response = None

        self.msg_prompt_shown = False
        self.import_action = None

        self.show_buttons()

To use …

self.messagebox("some text", "Response")
self.messagebox("some warning text", "Warning"

Results:

before prompt …

after prompt is executed …