Как обрабатывать одновременно два события PyQt5

Пишу приложение по измерению скорости печати.
Столкнулся с проблемой: при введении текста в self.area_for_typing мне нужно обработать сразу два события (не уверен, что это так называется): start и contents_change.

Но из-за цикла в start работать корректно может только один из методов.

Я погуглил и узнал про возможность создания многопоточности, попытался ее сделать, но не получилось.

Как при введении текста сделать так, чтобы одновременно работал и start, и contents_change?

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys, random, time
from PyQt5.Qt import *
from PyQt5 import QtWidgets
from PyQt5.QtTest import *

class Window(QWidget):
    def __init__(self, parent = None):
        QWidget.__init__(self, parent)

        self.text_for_typing = QTextEdit(self)
        self.text_for_typing.setEnabled(False)
        self.text_for_typing.setGeometry(0, 53, 600, 244)
        self.text_for_typing.setStyleSheet("color: rgb(255,255,255); \
                                            font: 24pt Arial Rounded MT Bold;")
        self.numbers = "0123456789"

        self.wpm = QLabel(self)
        self.wpm.setGeometry(0,0, 197, 47)
        self.wpm.setStyleSheet("font: 28pt Arial Rounded MT Bold; \
                                color: rgb(255,0,0);")
        self.wpm.setText("<center>0 WPM</center>")

        self.secs = QLabel(self)
        self.secs.setGeometry(203, 0, 195, 47)
        self.secs.setStyleSheet("font: 28pt Arial Rounded MT Bold; \
                                 color: rgb(0,85,0);")
        self.secs.setText("<center>0 Sec</center>")

        self.accuracy = QLabel(self)
        self.accuracy.setGeometry(410, 0, 197, 47)
        self.accuracy.setStyleSheet("font: 28pt Arial Rounded MT Bold; \
                                     color: rgb(0,85,127);")
        self.accuracy.setText("<center>0 %</center>")
        
        self.area_for_typing = QTextEdit(self)
        self.area_for_typing.setGeometry(0,303,600,450)
        self.area_for_typing.setStyleSheet("color: rgb(170,170,127); \
                                            font: 16pt Arial Rounded MT Bold;")
        
        self.area_for_typing.document().contentsChange.connect(self.contents_change)
        
        self.format = QTextCharFormat()
        self.format.setFont(QFont("Arial", 22, QFont.Bold))

        self.setStyleSheet("border-radius: 10px; \
                            background-color: rgb(0, 0, 0);")
        self.setWindowTitle("TypingSpeed")
        self.setWindowIcon(QIcon("keyboard.ico"))
        self.resize(600,450)
        self.setFixedSize(self.size())
        self.generate_symbols()
        self.show()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setPen(QPen(Qt.white,4,Qt.SolidLine))
        
        painter.drawLine(0, 50, 600, 50)
        painter.drawLine(200,50,200,0)
        painter.drawLine(400, 50, 400, 0)
        painter.drawLine(0,300,600,300)

    def contents_change(self, position, charsRemoved, charsAdded):
        if not self.area_for_typing.document().toPlainText():
            return

        cursor = self.text_for_typing.textCursor()
        cursor.setPosition(position)

        end = cursor.movePosition(QTextCursor.NextCharacter, 1)
        self.text = self.text_for_typing.toPlainText()

        try:
            if end:
                letter_text = self.text[position]
                letter_area_for_typing = self.area_for_typing.document().toPlainText()[position]

                if letter_text == letter_area_for_typing:
                    self.format.setTextOutline(QPen(QColor("green")))

                else:
                    self.format.setTextOutline(QPen(QColor("red")))

        except:
            pass

        cursor.mergeCharFormat(self.format)

    def generate_symbols(self):
        text_for_typing = ""
        strings = []

        file = open("ENRUS .TXT", encoding = "utf-8")
        file.seek(0)

        for line in file:
                strings.append(line[:-1])

        for i in range(20):
            random_int = random.randint(0,97)
            word = strings[random_int]
            text_for_typing+= word + " "

        text_for_typing = text_for_typing[0].swapcase() + text_for_typing[1:]
        self.text_for_typing.setText(f"<center>{text_for_typing}</center>")

    def start(self):
        self.seconds = 0
        self.current_wpm = 0
        self.accuracy_per = 0

        if self.area_for_typing.toPlainText() != "":
            try:
                while True:
                    text_for_typing = self.text_for_typing.toPlainText()
                    area_text = self.area_for_typing.toPlainText()
                    correct_letters = 0
                    allow = False


                    for i in range(0,len(area_text)):
                        if area_text != "":
                            if text_for_typing[i] == area_text[i]:
                                correct_letters+=1

                    if len(text_for_typing)-1 <= len(area_text):
                        allow = True
                        break

                    if allow == False:
                        QTest.qWait(1000)
                        self.seconds+=1

                    if self.seconds > 0 and correct_letters > 0:
                        self.accuracy_per = round(( correct_letters / len(area_text) ) * 100)
                        self.current_wpm = round(((correct_letters/self.seconds)*60)/7.2) #7.2 - средняя длина русского слова

                    self.secs.setText(f"<center>{self.seconds} Sec</center>")
                    self.wpm.setText(f"<center>{self.current_wpm} WPM</center>")
                    self.accuracy.setText(f"<center>{self.accuracy_per} %</center>")
                    
                    qApp.processEvents()

            except:
                pass

            if len(text_for_typing)-1 <= len(area_text):
                self.text_for_typing.clear()
                self.area_for_typing.clear()
                
                self.generate_symbols()
                
                self.wpm.setText("<center>0 WPM</center>")
                self.secs.setText("<center>0 Sec</center>")
                self.accuracy.setText("<center>0 %</center>")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    sys.exit(app.exec_())

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

Автор решения: S. Nick

Я не сильно вникал в то что вы написали сегодня.

Запомните, никогда не используйте циклы while True и time.sleep() в основном потоке приложения.

Если какие-либо расчеты занимают много времени - вам нужен дополнительный поток.

Если расчеты НЕ занимают много времени, но требую периодического выполнения (это ваш случай) - вам нужен класс QTimer.

import sys
import random
#import time
from PyQt5.Qt import *
from PyQt5 import QtWidgets
#from PyQt5.QtTest import *


class Window(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.text_for_typing = QTextEdit(self)
        self.text_for_typing.setEnabled(False)
        self.text_for_typing.setGeometry(0, 53, 600, 244)
        self.text_for_typing.setStyleSheet("color: rgb(255,255,255); \
                                            font: 17pt Arial Rounded MT Bold;")
        self.numbers = "0123456789"

        self.wpm = QLabel(self)
        self.wpm.setGeometry(0,0, 197, 47)
        self.wpm.setStyleSheet("font: 28pt Arial Rounded MT Bold; \
                                color: rgb(255,0,0);")
        self.wpm.setText("<center>0 WPM</center>")

        self.secs = QLabel(self)
        self.secs.setGeometry(203, 0, 195, 47)
        self.secs.setStyleSheet("font: 28pt Arial Rounded MT Bold; \
                                 color: rgb(0,85,0);")
        self.secs.setText("<center>0 Sec</center>")

        self.accuracy = QLabel(self)
        self.accuracy.setGeometry(410, 0, 197, 47)
        self.accuracy.setStyleSheet("font: 28pt Arial Rounded MT Bold; \
                                     color: rgb(0,85,127);")
        self.accuracy.setText("<center>0 %</center>")
        
        self.area_for_typing = QTextEdit(self)
        self.area_for_typing.setGeometry(0,303,600,450)
        self.area_for_typing.setStyleSheet("color: rgb(170,170,127); \
                                            font: 16pt Arial Rounded MT Bold;")
        
        self.area_for_typing.document().contentsChange.connect(self.contents_change)
        
        self.format = QTextCharFormat()
        self.format.setFont(QFont("Arial", 22, QFont.Bold))

        self.setStyleSheet("border-radius: 10px; \
                            background-color: rgb(0, 0, 0);")
        self.setWindowTitle("TypingSpeed")
        self.setWindowIcon(QIcon("keyboard.ico"))
        self.resize(600, 450)
#        self.setFixedSize(self.size())
        self.generate_symbols()
#        self.show()
# +++        
        self.timer = QTimer()                                          # +++
        self.timer.timeout.connect(self.start)                         # +++

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setPen(QPen(Qt.white,4,Qt.SolidLine))
        
        painter.drawLine(0, 50, 600, 50)
        painter.drawLine(200, 50, 200, 0)
        painter.drawLine(400, 50, 400, 0)
        painter.drawLine(0, 300, 600, 300)

    def contents_change(self, position, charsRemoved, charsAdded):
        if not self.area_for_typing.document().toPlainText():
            # ???
            return
# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
        if len(self.area_for_typing.document().toPlainText()) == 1:
            self.seconds = 0
            self.current_wpm = 0
            self.accuracy_per = 0
            self.timer.start(1000)
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

        cursor = self.text_for_typing.textCursor()
        cursor.setPosition(position)
        end = cursor.movePosition(QTextCursor.NextCharacter, 1)
        self.text = self.text_for_typing.toPlainText()

        try:
            if end:
                letter_text = self.text[position]
                letter_area_for_typing = self.area_for_typing.document().toPlainText()[position]
                if letter_text == letter_area_for_typing:
                    self.format.setTextOutline(QPen(QColor("green")))
                else:
                    self.format.setTextOutline(QPen(QColor("red")))
        except:
            pass

        cursor.mergeCharFormat(self.format)

    def generate_symbols(self):
        text_for_typing = ""
        strings = []
#                          txt 
        file = open("ENRUS.txt", encoding = "utf-8")
        file.seek(0)

        for line in file:
                strings.append(line[:-1])

        for i in range(20):
#            random_int = random.randint(0, 97)
            random_int = random.randint(0, 20)
            
            word = strings[random_int]
            text_for_typing+= word + " "

        text_for_typing = text_for_typing[0].swapcase() + text_for_typing[1:]
        self.text_for_typing.setText(f"<center>{text_for_typing}</center>")

    def start(self):
        print(f'def start(self): ==================== работает') #
#        self.seconds = 0
#        self.current_wpm = 0
#        self.accuracy_per = 0

        if self.area_for_typing.toPlainText() != "":
            try:
# ???                while True:
                text_for_typing = self.text_for_typing.toPlainText()
                area_text = self.area_for_typing.toPlainText()
                correct_letters = 0
                allow = False

                for i in range(0,len(area_text)):
                    if area_text != "":
                        if text_for_typing[i] == area_text[i]:
                            correct_letters+=1

                if len(text_for_typing)-1 <= len(area_text):
                    allow = True
# ??? SyntaxError: 'break' вне цикла
# ???                     break
                    return

                if allow == False:
# ???                    QTest.qWait(1000)
                    self.seconds += 1

                if self.seconds > 0 and correct_letters > 0:
                    self.accuracy_per = round(( correct_letters / len(area_text) ) * 100)
                    self.current_wpm = round(((correct_letters/self.seconds)*60)/7.2) #7.2 - средняя длина русского слова

                self.secs.setText(f"<center>{self.seconds} Sec</center>")
                self.wpm.setText(f"<center>{self.current_wpm} WPM</center>")
                self.accuracy.setText(f"<center>{self.accuracy_per} %</center>")
# ???                    qApp.processEvents()

            except:
                pass

            if len(text_for_typing)-1 <= len(area_text):
                self.text_for_typing.clear()
                self.area_for_typing.clear()
                
                self.generate_symbols()
                
                self.wpm.setText("<center>0 WPM</center>")
                self.secs.setText("<center>0 Sec</center>")
                self.accuracy.setText("<center>0 %</center>")
# +++                
                self.timer.stop()                                       # +++


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

введите сюда описание изображения

→ Ссылка