Как починить криво отражающийся текст в QPlainTextEdit в PyQt?

Немного контекста:

Приложение разбито на классы интерфейса, управления и внутренней логики. Они соединены с помощью паттерна "наблюдатель". Логика запускается в отдельном потоке через QThreadPool в классе управления. Периодически класс логики посылает сообщения интерфейсу, которые отображаются в QLineEdit и QPlainTextEdit, совсем не так, как надо. Так же, параллельно запущен поток, в котором происходит отслеживание нажатия сочетания клавиш. Если изменить размер интерфейса (потянув за край) - текст приходит в норму.

Подозреваю, что проблема может быть в работе потоков, но не могу понять, в чём именно. Буду очень признателен за помощь.

Реализация "наблюдателя":

class Observer(metaclass=ABCMeta):
    @abstractmethod
    def event_happened(self, text: str) -> None:
        pass

class Observable(metaclass=ABCMeta):
    def __init__(self):
        self._list_observers: List[Observer] = []

    def copy_list(self, list_observers: List[Observer]):
        self._list_observers = list_observers

    def get_list(self):
        return self._list_observers

    def attach(self, observer: Observer) -> None:
        self._list_observers.append(observer)

    def detach(self, observer: Observer) -> None:
        if observer in self._list_observers:
            self._list_observers.remove(observer)

    def notify_event_happened(self, text: str) -> None:
        for observer in self._list_observers:
            observer.event_happened(text)

В дальнейшем Observer наследуется и метод event_happened определяется в каждом конкретном классе.

Ввод текст в QPlainTextEdit:

class View(QtWidgets.QMainWindow, view.Ui_MainWindow, Observer, metaclass=Meta):
    ...
    def update_log(self, event: str) -> None:
        self.text_log.appendPlainText(event)
        self.text_log.update()

Запуск логики и отслеживания нажатия сочетания в отдельных потоках:

class Controller:
    def __init__(self, model: Model):
        ...
        self._threadpoll = PyQt5.QtCore.QThreadPool()

    def start_request(self) -> None:
        self.update_model()
        self._threadpoll.start(self._model.start)
        self._threadpoll.start(self._model.wait_signal_about_escape)

Кривой текст в QLineEdit и QPlainTextEdit


Ответы (1 шт):

Автор решения: Maxim Timakov

С виджетами нельзя работать на прямую из потоков, отличных от потока UI.
Для обхода этого ограничения нужно использовать очереди событий.

Попробуйте так:

class View(QtWidgets.QMainWindow, view.Ui_MainWindow, Observer, metaclass=Meta):
    update_log = Signal(str)
    
    def _init__(self, ...):
        # ....
        self.update_log.connect(self.do_update_log)
    #...
    
    def event_happened(self, text: str) -> None:
        self.update_log.emit(text)
    
    @Slot(str)
    def do_update_log(self, event: str) -> None:
        self.text_log.appendPlainText(event)
        self.text_log.update()

"Синтаксис" сигналов/слотов из PySide2: для PyQt5 нужно будет объявить псевдонимы:

from PyQt5.QtCore import \
    pyqtSlot as Slot, pyqtSignal as Signal
→ Ссылка