how to dynamically create 2 to 4 QTimers based on attributes in a json file
-
Thank you both @SGaist and @enjoysmath !
I restructured code as SGaist suggested, created a list of boards and iterated over the board list.
Like enjoysmath said, I also encountered the issue of only the last board was updated in the singleShot() method. Using a lambda function and grab argument fixed the issue.Seems like for entry timers I have a working solution now at least on my machine. Below is my code if anyone would like to take a look: Note that it is not a Minimal, Reproducible Example because my app includes some other functions and event handlers.
def showTrial1(self, qjson): self.ui.stackedWidget.setCurrentWidget(self.ui.trial1) rulesets = qjson["rulesets"].toObject() boardArr = qjson["boards"].toArray() for b in boardArr: board = b.toObject() bd = Board(rulesets, board, self.geometry()) self.boards.append(bd) self.ui.stackedWidget.addWidget(bd) for bd in self.boards: boardEntrytimer = QTimer(self) boardEntrytimer.singleShot(int(bd.entry_time)*1000, lambda arg = bd: self.createBoard(arg)) def createBoard(self,board): # print("updated! " + getTimestamp()) self.ui.stackedWidget.setCurrentWidget(board) -
Thank you both @SGaist and @enjoysmath !
I restructured code as SGaist suggested, created a list of boards and iterated over the board list.
Like enjoysmath said, I also encountered the issue of only the last board was updated in the singleShot() method. Using a lambda function and grab argument fixed the issue.Seems like for entry timers I have a working solution now at least on my machine. Below is my code if anyone would like to take a look: Note that it is not a Minimal, Reproducible Example because my app includes some other functions and event handlers.
def showTrial1(self, qjson): self.ui.stackedWidget.setCurrentWidget(self.ui.trial1) rulesets = qjson["rulesets"].toObject() boardArr = qjson["boards"].toArray() for b in boardArr: board = b.toObject() bd = Board(rulesets, board, self.geometry()) self.boards.append(bd) self.ui.stackedWidget.addWidget(bd) for bd in self.boards: boardEntrytimer = QTimer(self) boardEntrytimer.singleShot(int(bd.entry_time)*1000, lambda arg = bd: self.createBoard(arg)) def createBoard(self,board): # print("updated! " + getTimestamp()) self.ui.stackedWidget.setCurrentWidget(board) -
Thank you both @SGaist and @enjoysmath !
I restructured code as SGaist suggested, created a list of boards and iterated over the board list.
Like enjoysmath said, I also encountered the issue of only the last board was updated in the singleShot() method. Using a lambda function and grab argument fixed the issue.Seems like for entry timers I have a working solution now at least on my machine. Below is my code if anyone would like to take a look: Note that it is not a Minimal, Reproducible Example because my app includes some other functions and event handlers.
def showTrial1(self, qjson): self.ui.stackedWidget.setCurrentWidget(self.ui.trial1) rulesets = qjson["rulesets"].toObject() boardArr = qjson["boards"].toArray() for b in boardArr: board = b.toObject() bd = Board(rulesets, board, self.geometry()) self.boards.append(bd) self.ui.stackedWidget.addWidget(bd) for bd in self.boards: boardEntrytimer = QTimer(self) boardEntrytimer.singleShot(int(bd.entry_time)*1000, lambda arg = bd: self.createBoard(arg)) def createBoard(self,board): # print("updated! " + getTimestamp()) self.ui.stackedWidget.setCurrentWidget(board)@htjane I think ๐ญthat u're already doing it correctly โ. However, please tell us more about your bug. ๐
-
Thank you both @SGaist and @enjoysmath !
I restructured code as SGaist suggested, created a list of boards and iterated over the board list.
Like enjoysmath said, I also encountered the issue of only the last board was updated in the singleShot() method. Using a lambda function and grab argument fixed the issue.Seems like for entry timers I have a working solution now at least on my machine. Below is my code if anyone would like to take a look: Note that it is not a Minimal, Reproducible Example because my app includes some other functions and event handlers.
def showTrial1(self, qjson): self.ui.stackedWidget.setCurrentWidget(self.ui.trial1) rulesets = qjson["rulesets"].toObject() boardArr = qjson["boards"].toArray() for b in boardArr: board = b.toObject() bd = Board(rulesets, board, self.geometry()) self.boards.append(bd) self.ui.stackedWidget.addWidget(bd) for bd in self.boards: boardEntrytimer = QTimer(self) boardEntrytimer.singleShot(int(bd.entry_time)*1000, lambda arg = bd: self.createBoard(arg)) def createBoard(self,board): # print("updated! " + getTimestamp()) self.ui.stackedWidget.setCurrentWidget(board)@htjane Oh I see, you've already solved it. Told you 95% chance.
-
Be careful creating signal/slot connections in a Loop. At least in PyQt5 I always ran into the bug where all were connected to the last slot or something. Solved by more explicit lambda function capture parameters or something.
There's a 95% chance you'll also hit this bug.
@enjoysmath said in how to dynamically create 2 to 4 QTimers based on attributes in a json file:
Be careful creating signal/slot connections in a Loop. At least in PyQt5 I always ran into the bug where all were connected to the last slot or something. Solved by more explicit lambda function capture parameters or something.
That's unrelated to PyQt, and not a bug. Captures bind to variables, not the value they hold at the time of capture.
i = 0 function = lambda: print(i) i = 1 function()Output:
1 -
Thank you both @SGaist and @enjoysmath !
I restructured code as SGaist suggested, created a list of boards and iterated over the board list.
Like enjoysmath said, I also encountered the issue of only the last board was updated in the singleShot() method. Using a lambda function and grab argument fixed the issue.Seems like for entry timers I have a working solution now at least on my machine. Below is my code if anyone would like to take a look: Note that it is not a Minimal, Reproducible Example because my app includes some other functions and event handlers.
def showTrial1(self, qjson): self.ui.stackedWidget.setCurrentWidget(self.ui.trial1) rulesets = qjson["rulesets"].toObject() boardArr = qjson["boards"].toArray() for b in boardArr: board = b.toObject() bd = Board(rulesets, board, self.geometry()) self.boards.append(bd) self.ui.stackedWidget.addWidget(bd) for bd in self.boards: boardEntrytimer = QTimer(self) boardEntrytimer.singleShot(int(bd.entry_time)*1000, lambda arg = bd: self.createBoard(arg)) def createBoard(self,board): # print("updated! " + getTimestamp()) self.ui.stackedWidget.setCurrentWidget(board)@htjane said in how to dynamically create 2 to 4 QTimers based on attributes in a json file:
Thank you both @SGaist and @enjoysmath !
I restructured code as SGaist suggested, created a list of boards and iterated over the board list.
Like enjoysmath said, I also encountered the issue of only the last board was updated in the singleShot() method. Using a lambda function and grab argument fixed the issue.Seems like for entry timers I have a working solution now at least on my machine. Below is my code if anyone would like to take a look: Note that it is not a Minimal, Reproducible Example because my app includes some other functions and event handlers.
for bd in self.boards: boardEntrytimer = QTimer(self) boardEntrytimer.singleShot(int(bd.entry_time)*1000, lambda arg = > ```This code creates an extra QTimer on each loop iteration. QTimer.singleShot() is a static function.
-
@enjoysmath said in how to dynamically create 2 to 4 QTimers based on attributes in a json file:
Be careful creating signal/slot connections in a Loop. At least in PyQt5 I always ran into the bug where all were connected to the last slot or something. Solved by more explicit lambda function capture parameters or something.
That's unrelated to PyQt, and not a bug. Captures bind to variables, not the value they hold at the time of capture.
i = 0 function = lambda: print(i) i = 1 function()Output:
1@jeremy_k said in how to dynamically create 2 to 4 QTimers based on attributes in a json file:
That's unrelated to PyQt, and not a bug.
@enjoysmath will be thinking of is the way Python (nothing to do with PyQt, and agreed not a "bug") works for the following pattern:
for i in range(10): list_of_buttons[i].clicked.connect(lambda: print(i))I and others started out writing this (posts in this forum and stackoverflow), expecting it have the lambda-slot tell us which button was clicked. Like:
for (int i = 0; i < 10; i++) connect(this, list_of_buttons[i], [i]() { qDebug() << i; });which is what people expect it to do.
I guess Python passes something like
&iwould do in C++. The Python user needs:list_of_buttons[i].clicked.connect(lambda ii=i: print(ii))which people do not realise at first, and I imagine @enjoysmath has this in mind.
-
Hi all,
Sorry I'm new to Qt. Here again asking a follow-up question about reading board instance attributes that have just been modified by keypress event. Not sure if I should post it here or open a new thread. Please Lmk.My question is in the KeyPressEvent of Board class, I set the self.finish attribute to True to indicate the target shape has been clicked and a key pressed, then in MyApp class I want to get the finish attribute of board instance using an if-condition to check if the board has been finished. But the code in MyApp executes before "finish" has been modified by KeyPressEvent, without hanging or waiting for it and "finish" is False whenever I print it in MyApp class. Specifically, I want to read "finish" of each board in MyApp and if it's True, set the entry_time of the next board to 0.5s to prevent waiting this board's duration to expire. Could you guys suggest some solutions?
class MyApp(QMainWindow): # constructor and some functions... def showTrial1(self, qjson): self.ui.stackedWidget.setCurrentWidget(self.ui.trial1) rulesets = qjson["rulesets"].toObject() boardArr = qjson["boards"].toArray() for b in boardArr: # create boards board = b.toObject() bd = Board(rulesets, board, self.geometry()) self.boards.append(bd) self.ui.stackedWidget.addWidget(bd) for bd in self.boards: #call a static timer to draw board based on entry_time QTimer.singleShot(int(bd.entry_time) * 1000, lambda arg=bd: self.createBoard(arg)) def createBoard(self, board): board.drawShapes() self.ui.stackedWidget.setCurrentWidget(board) class Board(QWidget): def __init__(self, rulesets, board, size, parent=None): super().__init__(parent) self.rules = rulesets self.lastLeftPoint = QPoint(0, 0) self.id = board["id"].toInt() self.duration = 10 # board["duration"] self.entry_time = board["entry_time"].toDouble() self.stimuli = board["stimuli"] self.shapes = [] self.click = False self.boardSize = size self.timeout = False self.finish = False def drawShapes(self): stiArr = self.stimuli.toArray() for s in stiArr: stimuli = s.toObject() ID = stimuli["id"].toInt() # print(ID) color = QtGui.QColor(stimuli["color"].toObject()["color"].toString()) text = stimuli["text"].toString() x = math.floor( stimuli[ "column"].toInt() * self.boardSize.width() / 12.0 - self.boardSize.width() / 24.0) # 3862 * 2122 y = math.floor(stimuli["row"].toInt() * self.boardSize.height() / 12.0 - self.boardSize.height() / 24.0) pos = QtCore.QPoint(x, y) target = stimuli["target"] response = stimuli["response"] # hue = clr["hue"].toInt() # saturation = clr["saturation"].toInt() * 255 # value = clr["value"].toInt() * 255 # alpha = clr["alpha"].toInt() * 255 if stimuli["shape"] == "circle": self.shapes.append(Circle(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "cross": self.shapes.append(Cross(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "star": self.shapes.append(Star(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "hexagon": self.shapes.append(Hexagon(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "octagon": self.shapes.append(Octagon(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "pentagon": self.shapes.append(Pentagon(ID, 30, pos, color, text, target, response)) # self.update() def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) if self.timeout: print("cleared! " + getTimestamp()) painter.eraseRect(0, 0, self.boardSize.width(), self.boardSize.height()) return for shape in self.shapes: shape.paint(painter) if self.click: painter.setPen(QPen(QColor("green"), 4, Qt.SolidLine)) painter.drawRect(self.lastLeftPoint.x() - 15, self.lastLeftPoint.y() - 15, 30, 30) painter.end() def mousePressEvent(self, event): # on left mouse click, it returns the coordinates if event.button() == Qt.LeftButton: self.lastLeftPoint = event.pos() for shape in self.shapes: # iterate through shapes if shape.position.x() - 15 <= self.lastLeftPoint.x() <= shape.position.x() + 15 and shape.position.y() - 15 <= self.lastLeftPoint.y() <= shape.position.y() + 15: if shape.target.toBool(): # if is target print("clicked the target!") self.click = True # set click to true else: print("not this one") self.update() def keyPressEvent(self, e): rules = self.rules["responserules"].toArray() # get response rules of the board if self.click: if "any" == rules[0].toString() and type(e.key()) is int: # if rule is any and pressed any key self.finish = True print("updated") # verified that this is hit by printing statement else: myList = [Qt.Key_Q, Qt.Key_W, Qt.Key_E, Qt.Key_R, Qt.Key_T] if e.text() in myList: print("In My List") # if clicked a key in the set -
Hi all,
Sorry I'm new to Qt. Here again asking a follow-up question about reading board instance attributes that have just been modified by keypress event. Not sure if I should post it here or open a new thread. Please Lmk.My question is in the KeyPressEvent of Board class, I set the self.finish attribute to True to indicate the target shape has been clicked and a key pressed, then in MyApp class I want to get the finish attribute of board instance using an if-condition to check if the board has been finished. But the code in MyApp executes before "finish" has been modified by KeyPressEvent, without hanging or waiting for it and "finish" is False whenever I print it in MyApp class. Specifically, I want to read "finish" of each board in MyApp and if it's True, set the entry_time of the next board to 0.5s to prevent waiting this board's duration to expire. Could you guys suggest some solutions?
class MyApp(QMainWindow): # constructor and some functions... def showTrial1(self, qjson): self.ui.stackedWidget.setCurrentWidget(self.ui.trial1) rulesets = qjson["rulesets"].toObject() boardArr = qjson["boards"].toArray() for b in boardArr: # create boards board = b.toObject() bd = Board(rulesets, board, self.geometry()) self.boards.append(bd) self.ui.stackedWidget.addWidget(bd) for bd in self.boards: #call a static timer to draw board based on entry_time QTimer.singleShot(int(bd.entry_time) * 1000, lambda arg=bd: self.createBoard(arg)) def createBoard(self, board): board.drawShapes() self.ui.stackedWidget.setCurrentWidget(board) class Board(QWidget): def __init__(self, rulesets, board, size, parent=None): super().__init__(parent) self.rules = rulesets self.lastLeftPoint = QPoint(0, 0) self.id = board["id"].toInt() self.duration = 10 # board["duration"] self.entry_time = board["entry_time"].toDouble() self.stimuli = board["stimuli"] self.shapes = [] self.click = False self.boardSize = size self.timeout = False self.finish = False def drawShapes(self): stiArr = self.stimuli.toArray() for s in stiArr: stimuli = s.toObject() ID = stimuli["id"].toInt() # print(ID) color = QtGui.QColor(stimuli["color"].toObject()["color"].toString()) text = stimuli["text"].toString() x = math.floor( stimuli[ "column"].toInt() * self.boardSize.width() / 12.0 - self.boardSize.width() / 24.0) # 3862 * 2122 y = math.floor(stimuli["row"].toInt() * self.boardSize.height() / 12.0 - self.boardSize.height() / 24.0) pos = QtCore.QPoint(x, y) target = stimuli["target"] response = stimuli["response"] # hue = clr["hue"].toInt() # saturation = clr["saturation"].toInt() * 255 # value = clr["value"].toInt() * 255 # alpha = clr["alpha"].toInt() * 255 if stimuli["shape"] == "circle": self.shapes.append(Circle(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "cross": self.shapes.append(Cross(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "star": self.shapes.append(Star(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "hexagon": self.shapes.append(Hexagon(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "octagon": self.shapes.append(Octagon(ID, 30, pos, color, text, target, response)) elif stimuli["shape"] == "pentagon": self.shapes.append(Pentagon(ID, 30, pos, color, text, target, response)) # self.update() def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) if self.timeout: print("cleared! " + getTimestamp()) painter.eraseRect(0, 0, self.boardSize.width(), self.boardSize.height()) return for shape in self.shapes: shape.paint(painter) if self.click: painter.setPen(QPen(QColor("green"), 4, Qt.SolidLine)) painter.drawRect(self.lastLeftPoint.x() - 15, self.lastLeftPoint.y() - 15, 30, 30) painter.end() def mousePressEvent(self, event): # on left mouse click, it returns the coordinates if event.button() == Qt.LeftButton: self.lastLeftPoint = event.pos() for shape in self.shapes: # iterate through shapes if shape.position.x() - 15 <= self.lastLeftPoint.x() <= shape.position.x() + 15 and shape.position.y() - 15 <= self.lastLeftPoint.y() <= shape.position.y() + 15: if shape.target.toBool(): # if is target print("clicked the target!") self.click = True # set click to true else: print("not this one") self.update() def keyPressEvent(self, e): rules = self.rules["responserules"].toArray() # get response rules of the board if self.click: if "any" == rules[0].toString() and type(e.key()) is int: # if rule is any and pressed any key self.finish = True print("updated") # verified that this is hit by printing statement else: myList = [Qt.Key_Q, Qt.Key_W, Qt.Key_E, Qt.Key_R, Qt.Key_T] if e.text() in myList: print("In My List") # if clicked a key in the set -
@htjane Why don't you simply emit a "finished" signal to notify others that it is finished? This is how Qt applications (or any other event driven applications) work.