PyQt6 如何尽可能快地同时更新 96 个小部件?

问题描述 投票:0回答:1

这是关于我的 GUI 应用程序。

您所看到的是我创建的窗口,用于让用户自定义应用程序的样式。我不知道好看不好看,但是我已经尽力按照我的审美让它看起来最好了

窗口分为 9 个区域,每个区域用于更改一组相关小部件的样式。小部件的预览位于每个区域的左侧,每个区域的右侧是滚动区域,其中包含可更改样式某个方面的小部件。

可用于自定义的选项,或者可以更改的小部件样式的属性,如果小部件使用平面背景,则为边框样式、边框颜色、文本颜色和背景颜色,否则为渐变的起始颜色如果背景使用渐变,则为渐变的中间颜色。渐变有 3 个停止点,第一个和最后一个停止点使用相同的颜色。

对于小部件可能处于的所有可能状态中的每个状态,这些属性都可以独立更改。

有两种类型的可以更改样式的小部件。一种类型改变颜色,另一种改变边框样式。边框样式通过 QComboBox 更改,并且可以使用 QLineEdit 和 QColorDialog 更改颜色,通过单击关联的按钮调用该对话框。

每个组控制下划线字典中存储样式配置的一个键,当组的状态改变时,相应的键改变并使用类编译样式,然后更新窗口。

当点击开始按钮时,其文本将变为停止,并且预览区域中的许多小部件将每 125 毫秒更新一次。棋盘的动画是我编写的两个人工智能之间的实时井字游戏。按暂停按钮,它变成恢复按钮,动画暂停。按恢复按钮,动画将恢复。按下停止按钮,一切都会停止并重置。

按下随机化按钮,会生成一个随机样式,并且会立即更新所有更改样式的 96 个组,并应用该样式,如下所示:

它有效,问题是同时更新所有这些小部件会导致卡顿,窗口停止响应几秒钟,然后窗口被更新。我想消除滞后。

由于应用程序的大小,我不会在这里发布完整的代码。另外,由于同时更新 96 个小部件时会出现问题,因此我不会提供最小的可重现示例。该应用程序尚未完成,也未按预期工作,因此我还无法将其发布在代码审查上,事实上它几乎已经完成,当它完成时我会将其发布在代码审查上。

我将展示一些与此问题相关的代码片段,您将无法运行代码,我将完整的项目上传到Google Drive,以便您可以运行它,链接。到目前为止我实现的一切都完全正常,没有错误。

import asyncio
import qasync
import random
import sys
from concurrent.futures import ThreadPoolExecutor

class ColorGetter(Box):
    instances = []

    def __init__(self, widget: str, key: str, name: str):
        super().__init__()
        ColorGetter.instances.append(self)
        self.config = CONFIG[widget] if widget else CONFIG
        self.key = key
        self.name = name
        self.set_color(self.config[key])
        self.init_GUI()
        self.button.clicked.connect(self._show_color)
        self.picker.accepted.connect(self.pick_color)
        self.color_edit.returnPressed.connect(self.edit_color)

    def init_GUI(self):
        self.color_edit = ColorEdit(self.color_text)
        self.button = Button(self.color_text)
        self.vbox = make_vbox(self)
        self.vbox.addWidget(Label(self.name))
        self.vbox.addWidget(self.color_edit)
        self.vbox.addWidget(self.button)
        self.picker = ColorPicker()

    def set_color(self, text: str):
        self.color = [int(text[a:b], 16) for a, b in ((1, 3), (3, 5), (5, 7))]

    @property
    def color_text(self):
        r, g, b = self.color
        return f"#{r:02x}{g:02x}{b:02x}"

    def _show_color(self):
        self.picker.setCurrentColor(QColor(*self.color))
        self.picker.show()

    def _update(self):
        self.button.setText(self.color_text)
        self.config[self.key] = self.color_text
        GLOBALS["Window"].update_style()

    def pick_color(self):
        self.color = self.picker.currentColor().getRgb()[:3]
        self.color_edit.setText(self.color_text)
        self.color_edit.color = self.color_text
        self._update()

    def edit_color(self):
        text = self.color_edit.text()
        self.color_edit.color = text
        self.set_color(text)
        self.color_edit.clearFocus()
        self._update()

    def sync_config(self):
        color_html = self.config[self.key]
        self.color = [int(color_html[a:b], 16) for a, b in ((1, 3), (3, 5), (5, 7))]
        self.color_edit.setText(color_html)
        self.color_edit.color = color_html
        self.button.setText(color_html)


class BorderStylizer(Box):
    instances = []

    def __init__(self, widget: str, key: str, name: str):
        super().__init__()
        BorderStylizer.instances.append(self)
        self.config = CONFIG[widget] if widget else CONFIG
        self.key = key
        self.name = name
        self.borderstyle = self.config[key]
        self.init_GUI()

    def init_GUI(self):
        self.vbox = make_vbox(self)
        self.vbox.addWidget(Label(self.name))
        self.combobox = ComboBox(BORDER_STYLES)
        self.vbox.addWidget(self.combobox)
        self.combobox.setCurrentText(self.borderstyle)
        self.combobox.currentTextChanged.connect(self._update)

    def _update(self):
        self.borderstyle = self.combobox.currentText()
        self.config[self.key] = self.borderstyle
        GLOBALS["Window"].update_style()

    def sync_config(self):
        self.borderstyle = self.config[self.key]
        self.combobox.setCurrentText(self.borderstyle)


async def sync_config():
    loop = asyncio.get_event_loop()
    with ThreadPoolExecutor(max_workers=64) as executor:
        await asyncio.gather(
            *(
                loop.run_in_executor(executor, instance.sync_config)
                for instance in ColorGetter.instances + BorderStylizer.instances
            )
        )


def random_style():
    for entry in CONFIG.values():
        for k in entry:
            entry[k] = (
                random.choice(BORDER_STYLES)
                if k == "borderstyle"
                else f"#{random.randrange(16777216):06x}"
            )

    asyncio.run(sync_config())

    GLOBALS["Window"].update_style()
    GLOBALS["Window"].qthread.change.emit()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    if sys.platform == "win32":
        asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())

    loop = qasync.QEventLoop(app)
    asyncio.set_event_loop(loop)

我尝试通过异步运行更新来消除滞后,但效果不太好。

什么是更好的解决方案?

python asynchronous python-asyncio pyqt6
1个回答
0
投票

我解决了这个问题。感谢@musicamante 的评论。

所以问题非常简单,只是每次调用

random_style
时,每个组合框的文本都会更改,并且文本更改会导致每个组合框对窗口进行新的额外
update_style
调用。

所以我只是在进行更改之前断开每个组合框的

.currentTextChanged
信号,然后重新连接,如下所示:

    def sync_config(self):
        self.borderstyle = self.config[self.key]
        self.combobox.currentTextChanged.disconnect(self._update)
        self.combobox.setCurrentText(self.borderstyle)
        self.combobox.currentTextChanged.connect(self._update)

我尝试同步执行,没有发现明显的滞后,所以我摆脱了异步循环。

def random_style():
    for entry in CONFIG.values():
        for k in entry:
            entry[k] = (
                random.choice(BORDER_STYLES)
                if k == "borderstyle"
                else f"#{random.randrange(16777216):06x}"
            )

    for instance in ColorGetter.instances + BorderStylizer.instances:
        instance.sync_config()

    GLOBALS["Window"].update_style()
    GLOBALS["Window"].qthread.change.emit()
© www.soinside.com 2019 - 2024. All rights reserved.