Sending information from model to view, MVC architecture, PyQT5 - python

I'm trying to create an application with PyQT5 using the MVC architecture and I'm having trouble understanding how to send some of the information from the model to the view. In the sample application below, I have a countdown timer running in the model and I'd like the progress bar in the View to track it's progress. But I'm unsure how to send information to the view from the model when the method in the model is being executed. In my case, the progress_bar gets updated only after the execution of the model.counter method has completed executed. What would be a graceful way of handling this ? I need the progress_bar to update as model.i gets updated.
import sys
import time
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt5.QtWidgets import QGridLayout, QLineEdit, QPushButton, QVBoxLayout, QProgressBar
from PyQt5.QtCore import Qt
class ExampleGUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Sample Application')
# Set some main window's properties
self.setFixedSize(235, 235)
# Set the central widget and the general layout
self.generalLayout = QVBoxLayout()
self._centralWidget = QWidget(self)
self.setCentralWidget(self._centralWidget)
self._centralWidget.setLayout(self.generalLayout)
# Create the display and the buttons
self._createDisplay()
self._createButtons()
def _createDisplay(self):
"""Create the display."""
# Create the display widget
self.display = QLineEdit()
self.progress_bar = QProgressBar()
# Set some display's properties
self.display.setFixedHeight(35)
self.display.setAlignment(Qt.AlignRight)
self.display.setReadOnly(True)
# Add the display to the general layout
self.generalLayout.addWidget(self.display)
self.generalLayout.addWidget(self.progress_bar)
def _createButtons(self):
"""Create the buttons."""
buttonsLayout = QGridLayout()
self.button1 = QPushButton("Start")
self.button1.setFixedSize(80, 40)
self.button2 = QPushButton("Clear")
self.button2.setFixedSize(80, 40)
buttonsLayout.addWidget(self.button1)
buttonsLayout.addWidget(self.button2)
self.generalLayout.addLayout(buttonsLayout)
def setDisplayText(self, text):
"""Set display's text."""
self.display.setText(text)
self.display.setFocus()
def clearDisplay(self):
"""Clear the display."""
self.setDisplayText("")
class Model:
def __init__(self):
self.counter = ''
self.i = ''
def countdown(self, counter):
self.i = 0
self.counter = counter
while self.i < self.counter:
self.i+=1
time.sleep(1)
return True
class Controller:
def __init__(self, view, model):
self._view = view
self._model = model
self._connect_settings_signals()
def _set_message(self):
self._view.progress_bar.setMaximum(10)
reply = self._model.countdown(10)
self._view.progress_bar.setValue(self._model.i)
if reply:
self._view.setDisplayText("Countdown complete!")
def _clear_message(self):
self._view.clearDisplay()
def _connect_settings_signals(self):
self._view.button1.clicked.connect(self._set_message)
self._view.button2.clicked.connect(self._clear_message)
def main():
"""Main function."""
# Create an instance of `QApplication`
pycalc = QApplication(sys.argv)
# Show the calculator's GUI
view = ExampleGUI()
view.show()
model = Model()
# Create instances of the model and the controller
ctrl = Controller(view=view, model=model)
# Execute calculator's main loop
sys.exit(pycalc.exec_())
if __name__ == "__main__":
main()

That sleep(1) is blocking operation and event loop returns to '_set_message' only after 'countdown' is finished.
I would move update of model and loop out of model, back to controller. (As its kind of controlling, telling model update its value, wait a bit...)
I would update to something similar as:
def _set_message(self):
self._view.progress_bar.setMaximum(10)
self.i = 0
while self.i < 10:
self.i+=1
self._model.i += 1
self._view.progress_bar.setValue(self._model.i)
time.sleep(1)
self._view.setDisplayText("Countdown complete!")
If you would insist on looping and sleeping in model (I don't think thats a good idea), you would have to dive into threading, create separate thread for that operation, and keep main event loop unblocked for redrawing of progress bar. (imho)

Related

Pyqt5 - how to go back to hided Main Window from Secondary Window?

If I click Back from the second window, the program will just exit. How do I go back to mainwindow in this case? I assume I will need some more code in that clickMethodBack function.
import os
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QWidget, QLabel, QPushButton
import time
from PyQt5.QtCore import QSize
class GUI_Window():
def __init__(self):
self.main_window()
return
def main_window(self):
app = PyQt5.QtWidgets.QApplication(sys.argv)
self.MainWindow = MainWindow_()
self.MainWindow.show()
app.exec_()
return
class MainWindow_(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.TestAButton = QPushButton("TestA", self)
self.TestAButton.clicked.connect(self.TestA_clickMethod)
self.TestAButton.move(20, 0)
self.CloseButton = QPushButton("Close", self)
self.CloseButton.clicked.connect(self.Close_clickMethod)
self.CloseButton.move(20, 40)
self.TestingA = TestA_MainWindow()
def TestA_clickMethod(self):
self.TestAButton.setEnabled(False)
time.sleep(0.2)
self.TestingA.show()
self.hide()
try:
if self.TestingA.back == True:
self.show()
except:
None
def Close_clickMethod(self):
self.Test_Choice = 'Exit'
self.close()
class TestA_MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setMinimumSize(QSize(980,700))
self.setWindowTitle("TestA")
self.Back_Button = False
self.closeButton = QPushButton("Close", self)
self.closeButton.clicked.connect(self.clickMethodClose)
self.returnButton = QPushButton("Back", self)
self.returnButton.clicked.connect(self.clickMethodBack)
self.returnButton.move(0,30)
def clickMethodClose(self):
self.Back_Button = False
self.close()
def clickMethodBack(self):
self.returnButton.setEnabled(False)
time.sleep(0.5)
self.back = True
self.close()
# Run if Script
if __name__ == "__main__":
main = GUI_Window() # Initialize GUI
Your code has two very important issues.
you're using a blocking function, time.sleep; Qt, as almost any UI toolkit, is event driven, which means that it has to be able to constantly receive and handle events (coming from the system or after user interaction): when something blocks the event queue, it completely freezes the whole program until that block releases control;
you're checking for the variable too soon: even assuming the sleep would work, you cannot know if the window is closed after that sleep timer has ended;
The solution is to use signals and slots. Since you need to know when the second window has been closed using the "back" button, create a custom signal for the second window that will be emitted whenever the function that is called by the button is closed.
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(central)
self.testButton = QtWidgets.QPushButton('Test A')
self.closeButton = QtWidgets.QPushButton('Close')
layout.addWidget(self.testButton)
layout.addWidget(self.closeButton)
self.setCentralWidget(central)
self.testButton.clicked.connect(self.launchWindow)
self.closeButton.clicked.connect(self.close)
def launchWindow(self):
self.test = TestA_MainWindow()
self.test.backSignal.connect(self.show)
self.hide()
self.test.show()
class TestA_MainWindow(QtWidgets.QWidget):
backSignal = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
self.closeButton = QtWidgets.QPushButton('Close')
self.backButton = QtWidgets.QPushButton('Back')
layout.addWidget(self.closeButton)
layout.addWidget(self.backButton)
self.closeButton.clicked.connect(self.close)
self.backButton.clicked.connect(self.goBack)
def goBack(self):
self.close()
self.backSignal.emit()
def GUI_Window():
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
if __name__ == '__main__':
GUI_Window()
Notes:
I removed the GUI_Window class and made a function, as using a class for that is not really useful;
you should always prefer layout managers instead of setting manual geometries;
widgets should not be added to a QMainWindow as direct children, and a central widget should always be used (see the creation and use of central in the example); read more about it in the documentation;
only classes and constants should be capitalized, while variables, attributes and functions should always have names starting with a lowercase letter;

PyQt5 QMainwindow poped but hide [duplicate]

Hopefully I am following the guidelines correctly here with my first question. I am trying to create a GUI with the MVC structure. I am having difficulty with understanding why my signals are not always being picked up by the controller. I know that there is just something simple that I'm missing. I'm attaching code from a simple calculator which I used as a guide. I removed most of the features to simplify this as much as possible. It is now only 3 of the original buttons and my own button. For debugging, I just have the value on the button printed out when you press it.
import sys
# Import QApplication and the required widgets from PyQt5.QtWidgets
from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QMainWindow
from PySide2.QtWidgets import QWidget
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QGridLayout
from PySide2.QtWidgets import QLineEdit
from PySide2.QtWidgets import QPushButton
from PySide2.QtWidgets import QVBoxLayout
from functools import partial
ERROR_MSG = 'ERROR'
# Create a subclass of QMainWindow to setup the calculator's GUI
class PyCalcUi(QMainWindow):
"""PyCalc's View (GUI)."""
def __init__(self):
"""View initializer."""
super().__init__()
# Set some main window's properties
self.setWindowTitle('PyCalc')
self.setFixedSize(235, 235)
# Set the central widget and the general layout
self.generalLayout = QVBoxLayout()
self._centralWidget = QWidget(self)
self.setCentralWidget(self._centralWidget)
self._centralWidget.setLayout(self.generalLayout)
# Create the display and the buttons
self._createDisplay()
self._createButtons()
def _createDisplay(self):
"""Create the display."""
# Create the display widget
self.display = QLineEdit()
# Set some display's properties
self.display.setFixedHeight(35)
self.display.setAlignment(Qt.AlignRight)
self.display.setReadOnly(True)
# Add the display to the general layout
self.generalLayout.addWidget(self.display)
def _createButtons(self):
"""Create the buttons."""
self.buttons = {}
buttonsLayout = QGridLayout()
# Button text | position on the QGridLayout
buttons = {'7': (0, 0),
'8': (0, 1),
'9': (0, 2),
}
# Create the buttons and add them to the grid layout
for btnText, pos in buttons.items():
self.buttons[btnText] = QPushButton(btnText)
self.buttons[btnText].setFixedSize(40, 40)
buttonsLayout.addWidget(self.buttons[btnText], pos[0], pos[1])
self.mybutton = QPushButton("5")
buttonsLayout.addWidget(self.mybutton,1,0)
# Add buttonsLayout to the general layout
self.generalLayout.addLayout(buttonsLayout)
# Create a Controller class to connect the GUI and the model
class PyCalcCtrl:
"""PyCalc Controller class."""
def __init__(self, model, view):
"""Controller initializer."""
self._evaluate = model
self._view = view
# Connect signals and slots
self._connectSignals()
def _printthis(self):
print("Hi")
def _printthat(self, buttonvalue):
print(buttonvalue)
def _connectSignals(self):
"""Connect signals and slots."""
self._view.mybutton.clicked.connect(self._printthis)
for btnText, btn in self._view.buttons.items():
btn.clicked.connect(partial(self._printthat, btnText))
# Create a Model to handle the calculator's operation
def evaluateExpression(expression):
"""Evaluate an expression."""
try:
result = str(eval(expression, {}, {}))
except Exception:
result = ERROR_MSG
return result
# Client code
def main():
"""Main function."""
# Create an instance of QApplication if it doesn't exist
pycalc = QApplication.instance()
if pycalc is None:
pycalc = QApplication(sys.argv)
# Show the calculator's GUI
view = PyCalcUi()
view.show()
# Create instances of the model and the controller
model = evaluateExpression
PyCalcCtrl(model=model, view=view)
# Execute the calculator's main loop
sys.exit(pycalc.exec_())
if __name__ == '__main__':
main()
This set of code works, BUT if I comment out the
for btnText, btn in self._view.buttons.items():
btn.clicked.connect(partial(self._printthat, btnText))
The self._view.mybutton.clicked.connect(self._printthis) will no longer work.
What is the btn.clicked.connect(partial(self._printthat, btnText)) line doing which is allowing any other signal I put in def _connectSignals(self): to work. What aspect of that line is achieving something that the mybutton signal isn't doing?
The problem is caused because the PyCalcCtrl object is not assigned to a variable so it will be destroyed and therefore the "_printthis" method will not be accessible. On the other hand, when functools.partial is used then the object of the PyCalcCtrl class is assigned to the scope of that function, that's why it works.
The solution is to assign the PyCalcCtrl object to a variable:
ctrl = PyCalcCtrl(model=model, view=view)

Python PyQt signals are not always working

Hopefully I am following the guidelines correctly here with my first question. I am trying to create a GUI with the MVC structure. I am having difficulty with understanding why my signals are not always being picked up by the controller. I know that there is just something simple that I'm missing. I'm attaching code from a simple calculator which I used as a guide. I removed most of the features to simplify this as much as possible. It is now only 3 of the original buttons and my own button. For debugging, I just have the value on the button printed out when you press it.
import sys
# Import QApplication and the required widgets from PyQt5.QtWidgets
from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QMainWindow
from PySide2.QtWidgets import QWidget
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QGridLayout
from PySide2.QtWidgets import QLineEdit
from PySide2.QtWidgets import QPushButton
from PySide2.QtWidgets import QVBoxLayout
from functools import partial
ERROR_MSG = 'ERROR'
# Create a subclass of QMainWindow to setup the calculator's GUI
class PyCalcUi(QMainWindow):
"""PyCalc's View (GUI)."""
def __init__(self):
"""View initializer."""
super().__init__()
# Set some main window's properties
self.setWindowTitle('PyCalc')
self.setFixedSize(235, 235)
# Set the central widget and the general layout
self.generalLayout = QVBoxLayout()
self._centralWidget = QWidget(self)
self.setCentralWidget(self._centralWidget)
self._centralWidget.setLayout(self.generalLayout)
# Create the display and the buttons
self._createDisplay()
self._createButtons()
def _createDisplay(self):
"""Create the display."""
# Create the display widget
self.display = QLineEdit()
# Set some display's properties
self.display.setFixedHeight(35)
self.display.setAlignment(Qt.AlignRight)
self.display.setReadOnly(True)
# Add the display to the general layout
self.generalLayout.addWidget(self.display)
def _createButtons(self):
"""Create the buttons."""
self.buttons = {}
buttonsLayout = QGridLayout()
# Button text | position on the QGridLayout
buttons = {'7': (0, 0),
'8': (0, 1),
'9': (0, 2),
}
# Create the buttons and add them to the grid layout
for btnText, pos in buttons.items():
self.buttons[btnText] = QPushButton(btnText)
self.buttons[btnText].setFixedSize(40, 40)
buttonsLayout.addWidget(self.buttons[btnText], pos[0], pos[1])
self.mybutton = QPushButton("5")
buttonsLayout.addWidget(self.mybutton,1,0)
# Add buttonsLayout to the general layout
self.generalLayout.addLayout(buttonsLayout)
# Create a Controller class to connect the GUI and the model
class PyCalcCtrl:
"""PyCalc Controller class."""
def __init__(self, model, view):
"""Controller initializer."""
self._evaluate = model
self._view = view
# Connect signals and slots
self._connectSignals()
def _printthis(self):
print("Hi")
def _printthat(self, buttonvalue):
print(buttonvalue)
def _connectSignals(self):
"""Connect signals and slots."""
self._view.mybutton.clicked.connect(self._printthis)
for btnText, btn in self._view.buttons.items():
btn.clicked.connect(partial(self._printthat, btnText))
# Create a Model to handle the calculator's operation
def evaluateExpression(expression):
"""Evaluate an expression."""
try:
result = str(eval(expression, {}, {}))
except Exception:
result = ERROR_MSG
return result
# Client code
def main():
"""Main function."""
# Create an instance of QApplication if it doesn't exist
pycalc = QApplication.instance()
if pycalc is None:
pycalc = QApplication(sys.argv)
# Show the calculator's GUI
view = PyCalcUi()
view.show()
# Create instances of the model and the controller
model = evaluateExpression
PyCalcCtrl(model=model, view=view)
# Execute the calculator's main loop
sys.exit(pycalc.exec_())
if __name__ == '__main__':
main()
This set of code works, BUT if I comment out the
for btnText, btn in self._view.buttons.items():
btn.clicked.connect(partial(self._printthat, btnText))
The self._view.mybutton.clicked.connect(self._printthis) will no longer work.
What is the btn.clicked.connect(partial(self._printthat, btnText)) line doing which is allowing any other signal I put in def _connectSignals(self): to work. What aspect of that line is achieving something that the mybutton signal isn't doing?
The problem is caused because the PyCalcCtrl object is not assigned to a variable so it will be destroyed and therefore the "_printthis" method will not be accessible. On the other hand, when functools.partial is used then the object of the PyCalcCtrl class is assigned to the scope of that function, that's why it works.
The solution is to assign the PyCalcCtrl object to a variable:
ctrl = PyCalcCtrl(model=model, view=view)

Python program subclasses a PyQt5 Window cant setwindow.title in Function

I have a sample Python program that sub classes a PyQt5 window. I am teaching myself and still a little new PyQt5 and python Classes.
The program does what I want to do I ran into an error that I don't understand how to fix. To start this program functions as is, I am currently learning how to run Threads. I imported and sub classed the PyQt5 window. In the __init__ section of the subclass I can set the window title and it works fine.
If I move the statement to a function "def initUI(self):" I am unable to set the window title, mind you I have tried various versions of the statement and nothing works. It's the first line of the def and I have it commented out.
My questions are:
Is this property setable in a def.
If it io what is the proper format of the statement.
from PyQt5.QtCore import pyqtSlot, pyqtSignal
from PyQt5 import QtCore, QtGui, QtWidgets
from Threads import Ui_MainWindow
import sys, time
from time import sleep
class MainWindow_EXEC():
def __init__(self): # This section has to be laid out this way
app = QtWidgets.QApplication(sys.argv)
win = QtWidgets.QMainWindow()
self.ui = Ui_MainWindow()
self.ui.setupUi(win)
self.initUI() # Inits that need to happen before start up
win.setWindowTitle("This is the Title!") # Works fine
win.resize(800,600)
win.show()
sys.exit(app.exec_())
def initUI(self): # Button assignments
#self.ui.setWindowTitle("This is the Title!") # AttributeError: 'Ui_MainWindow' object has no attribute 'setWindowTitle'
self.ui.btn_Start.clicked.connect(self.start_progressbar)
self.ui.btn_Stop.clicked.connect(self.stop_progressbar)
self.ui.btn_Reset.clicked.connect(self.reset_progressbar)
self.progress_value = 0
self.stop_progress = False
def progressbar_counter(self, start_value=0):
# have to use member: self.run_thread NOT local var: run_thread
self.run_thread = RunThread(parent=None, counter_start=start_value)
self.run_thread.start()
self.run_thread.counter_value.connect(self.get_thread_value)
def get_thread_value(self, counter): #This updates the progress bar
print(counter)
if not self.stop_progress:
self.ui.progressBar.setValue(counter)
def start_progressbar(self): # This is the button that starts the progress bar
self.stop_progress = False # This is a switch
self.progress_value = self.ui.progressBar.value() # Updating the progress bar
self.progressbar_counter(self.progress_value)
def stop_progressbar(self): # This is a button to stop the progress bar
self.stop_progress = True
self.run_thread.stop()
def reset_progressbar(self): # This is a button to reset the progress bar
self.stop_progressbar()
self.progress_value = 0
self.stop_progress = False
self.ui.progressBar.reset()
class RunThread(QtCore.QThread):
counter_value = QtCore.pyqtSignal(int) # define new Signal
def __init__(self, parent=None, counter_start=0):
super(RunThread, self).__init__(parent)
self.counter = counter_start
self.isRunning = True
def run(self):
while self.counter < 100 and self.isRunning == True:
sleep(0.1)
self.counter += 1
print(self.counter)
self.counter_value.emit(self.counter) # emit new Signal with value
def stop(self):
self.isRunning = False
print('stopping thread...')
self.terminate()
if __name__ == "__main__":
MainWindow_EXEC()
The objective of the following classes must be distinguished:
QMainWindow is a widget that has the setWindowTitle method.
Ui_MainWindow is not a widget but a class that is used to fill a widget so it does not have the setWindowTitle method.
The solution is to make win a class member and then use that object to modify the title:
class MainWindow_EXEC():
def __init__(self): # This section has to be laid out this way
app = QtWidgets.QApplication(sys.argv)
self.win = QtWidgets.QMainWindow()
self.ui = Ui_MainWindow()
self.ui.setupUi(self.win)
self.initUI()
self.win.setWindowTitle("This is the Title!")
self.win.resize(800,600)
self.win.show()
sys.exit(app.exec_())
def initUI(self): # Button assignments
self.win.setWindowTitle("This is the Title!")
self.ui.btn_Start.clicked.connect(self.start_progressbar)
# ...

Passing multiple parameters back from PyQt thread

Is there a way to pass multiple parameters from a thread back to the main thread at the same time?
I have started a thread using PyQt5 in which two real time variables are calculated. I want to pass both parameters back to the main thread to be combined and calculated with parameters from another thread.
As attached in the code below, I am able to return each parameter individually and print to the screen. How do I return all parameters into one function so I can proceed with calculations from another thread?
Thank you!
import sys
import time
from PyQt5.QtWidgets import QMainWindow, QPushButton, QVBoxLayout, QFrame, QApplication
from PyQt5.QtCore import pyqtSignal, QObject, QThread
class Counter(QObject):
'''
Class intended to be used in a separate thread to generate numbers and send
them to another thread.
'''
param1 = pyqtSignal(str)
param2 = pyqtSignal(str)
stopped = pyqtSignal()
def __init__(self):
QObject.__init__(self)
def start(self):
'''
Count from 0 to 99 and emit each value to the GUI thread to display.
'''
for x in range(4):
self.param1.emit(str(x))
self.param2.emit(str(x)+'2')
time.sleep(0.7)
self.stopped.emit()
class Application(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
# Configuring widgets
self.button = QPushButton()
self.button.setText('99')
self.layout = QVBoxLayout()
self.layout.addWidget(self.button)
self.frame = QFrame()
self.frame.setLayout(self.layout)
self.setCentralWidget(self.frame)
# Configuring separate thread
self.counterThread = QThread()
self.counter = Counter()
self.counter.moveToThread(self.counterThread)
# Connecting signals
self.button.clicked.connect(self.startCounting)
self.counter.param1.connect(self.button.setText)
self.counter.param1.connect(self.someFunction1)
self.counter.param2.connect(self.someFunction2)
self.counter.stopped.connect(self.counterThread.quit)
self.counterThread.started.connect(self.counter.start)
# print data from parameter 1
def someFunction1(self, data):
print(data + ' in main')
# print data from parameter 2
def someFunction2(self, data):
print(data + ' in main')
def startCounting(self):
if not self.counterThread.isRunning():
self.counterThread.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Application()
window.show()
sys.exit(app.exec_())
The signals also support the transmission of lists so you can use it to transport several variables:
class Counter(QObject):
"""
Class intended to be used in a separate thread to generate numbers and send
them to another thread.
"""
params = pyqtSignal(list)
stopped = pyqtSignal()
def start(self):
"""
Count from 0 to 99 and emit each value to the GUI thread to display.
"""
for x in range(4):
values = [str(x), str(x) + "2"]
self.params.emit(values)
time.sleep(0.7)
self.stopped.emit()
class Application(QMainWindow):
def __init__(self):
super(Application, self).__init__()
# Configuring widgets
self.frame = QFrame()
self.button = QPushButton("99")
lay = QVBoxLayout(self.frame)
lay.addWidget(self.button)
self.setCentralWidget(self.frame)
# Configuring separate thread
self.counterThread = QThread()
self.counter = Counter()
self.counter.moveToThread(self.counterThread)
# Connecting signals
self.button.clicked.connect(self.startCounting)
self.counter.params.connect(self.someFunction)
self.counter.stopped.connect(self.counterThread.quit)
self.counterThread.started.connect(self.counter.start)
#pyqtSlot(list)
def someFunction(self, params):
print(params)
if params:
self.button.setText(params[0])
def startCounting(self):
if not self.counterThread.isRunning():
self.counterThread.start()

Categories

Resources