How to continue a loop after a button has been clicked? - python

I am trying to pause a loop and then continue the loop after a button is pressed.
I have a for loop that is fetching questions from a list and then display it for the user to answer,then continue when the user answers
how can I make the continue after the user clicks the button next.
this is my code below
from tkinter import *
class exam:
global nexq
def __init__(self,master):
self.master = master
self.master.title("tuples inside Lists")
self.master.geometry("300x300+0+0")
self.master.resizable(False,False)
self.panel = Frame(self.master,width=300,height=300,bg="brown")
self.panel.pack_propagate(0)
self.panel.pack(fill="both")
self.ans = IntVar()
self.board = Text(self.panel, width=40,height=10)
self.board.grid(rowspan=2,columnspan=3 )
self.opt1 = Radiobutton(self.panel,text="Nigeria",variable=self.ans,value=1,command=self.startexam)
self.opt1.grid(row=5,column=0,sticky=W)
self.opt2 = Radiobutton(self.panel,text="Ghana",variable=self.ans,value=2)
self.opt2.grid(row=5,column=2,sticky=W)
self.btnnext = Button(self.panel,text="next",command=self.nextq)
self.btnnext.grid(row=20,column=0,sticky=W)
def startexam(self):
global nexq
nexq = False
self.ans.set(0)
self.qstns = [('what is your name','john','philip','john'),
('where do you stay','Abuja','lagos','lagos'),
('what can you do','sing','program','program')]
for qustn,optn1,optn2,ans in self.qstns:
self.board.delete('1.0',END)
self.board.insert(END,qustn)
self.opt1.configure(text=optn1)
self.opt2.configure(text=optn2)
if not nexq:
break
else:
continue
def nextq(self):
global nexq
nexq = True
return True

As mentioned by Reblochon, you can use a generator to achieve pause/resume on a function by using yield. To understand how yield works, I highly recommend you to read through the highest voted Python post on SO here.
Below is a minimum sample using your questions as data:
import tkinter as tk
root = tk.Tk()
q = tk.Label(root,text="Question")
b = tk.Spinbox(root)
q.pack()
b.pack()
def ask_question():
qstns = [('what is your name','john','philip','john'),
('where do you stay','Abuja','lagos','lagos'),
('what can you do','sing','program','program')]
for i in qstns:
yield i[0], i[1:]
a = ask_question()
def get_next():
try:
start.config(text="Next question")
question, answer = next(a)
q.config(text=question)
b["value"] = answer
except StopIteration:
start.config(text="No more questions!",state="disabled",relief="sunken")
start = tk.Button(root,text="Start",command=get_next)
start.pack()
root.mainloop()

Related

Python: variable returns to the initial valor after loop changes

im trying to do a simple program in Python to send a message when my web valor changes (just context), the question is im doing a loop with tkinter to show a window, i have declared a function that is called in my tkinter loop, i have global variables, but when i change an global variable into the loop, at the next round the variable returns to the initial valor, here is my code:
#just variables (i have simplified the code)
aux1 = False
aux2 = False
aux3 = False
def search(url, aux):
try:
host_get = request.get(ip)
if host_get.status_code == 200:
host_cont = host.get.text
host_soup = BeautifulSoup(host_cont, 'lxml')
try:
host_stat = host_soup.find('div', class='UP'.getText())
aux = False
except:
if aux == False:
MessageBox.showWarning("Alert", "Host is DOWN")
aux = True
#The problem is here
#After doing this (aux = True), the program still doing (aux = False) options
elif aux == True:
print("Host still DOWN")
else:
MessageBox.showWarning("Alert", "Host is DOWN")
aux = True
def search_loop:
#i have many websites, but i showed only 1 for the example code
url1 = "website1.com"
global aux1
search(url1, aux1)
#5 seconds to do the next loop
time.sleep(5)
class WindowClass:
def __init__(self):
self.window1 = tk.Tk()
self.button1()
self.window1.mainloop()
def button1(self):
self.startbutton = tk.Button(self.window1, command=self.startloop, text='Start')
self.startbutton.place(x=50, y=50)
def startloop(self):
search_loop()
app1 = WindowClass()
yep, its a infinite loop, the problem i have is on the loop i think, but idk, i have tried many things to do it correctly but i cant, so arrived here, hope someone can help me,
sorry for the broken english, idk if i explained correctly.
you can't pass global variables around as parameters. You are passing a copy of the global variable into your search function, which is why the modification is lost.
Try changing you code so that you use the globals directly:
aux1 = False
aux2 = False
aux3 = False
def search():
global aux1, aux2, aux3
aux1 = True
class main():
def __init__(self) -> None:
search()
print(aux1)
print(aux2)
print(aux3)
# result
# True
# False
# False
main()

Multiple listboxs bound to same function

Honestly, I do not feel like this should be happening, but it is.
self.marketList.bind('<<ListboxSelect>>', self.market_selected)
self.jobsList.bind('<<ListboxSelect>>', self.job_selected)
There really isn't any more interaction between these two functions. When you click on an item in the marketList box, it's supposed to bring up the jobs in the jobsList box. Currently, it is applying the binding two both boxes. When I click on a job entry in the jobsBox, it clears the jobs and my troubleshooting is showing that it's calling market_selected. I'm not sure why this is happening, but it's really messing with what I'm trying to do with it.
How can I ensure that my binding is on only one widget, and won't be applied to multiple widgets?
edit:
I'm told that this isn't enough code to reproduce the error.
This is all the relevant code.
As I said previously, self.market_selected is called when I click on anything in the jobsList
edit #2
I uploaded the entire script.
import MarketWizard
import JobWizard
import SpanWalkerDocuments as swd
import tkinter as tk
from tkinter import *
from tkinter import ttk
import SpanWalkerDocuments as SpanWalker
class SpanWalker:
def __init__(self):
self.root = tk.Tk()
self.root.title('Luke Spanwalker')
self.root.resizable(True, True)
self.MainFrame = Frame(self.root, bg='red')
self.sidebarFrame = Frame(self.root, bg='blue')
self.tabControl = ttk.Notebook(self.sidebarFrame)
self.marketFrame = Frame(self.tabControl)
self.clientFrame = Frame(self.tabControl)
self.jobsFrame = Frame(self.tabControl)
self.polesFrame = Frame(self.MainFrame, height=100, width=50)
self.tabControl.add(self.marketFrame, text="Markets")
self.tabControl.add(self.jobsFrame, text="Jobs")
self.tabControl.add(self.clientFrame, text="Clients")
#self.tabControl.add(self.polesFrame, text="Poles")
self.tabControl.pack(expand=1, fill="both")
self.MainFrame.grid(row=0, column=1)
self.sidebarFrame.grid(row=0, column=0)
#Awesome, our tabbed control is ready.
#Now, we need to make the listBox widgets that will actually display our data.
self.marketList = Listbox(self.marketFrame)
self.jobsList = Listbox(self.jobsFrame)
self.polesList = Listbox(self.polesFrame)
#Binding functions! Yay for binding functions!
self.marketList.bind('<<ListboxSelect>>', self.market_selected)
self.jobsList.bind('<<ListboxSelect>>', self.job_selected)
self.curMarket = ""
self.markets = []
self.jobs = []
self.poles = []
def UpdateMarkets (self):
if len(self.markets) > 0:
self.markets.clear()
self.marketList.delete(0, END)
for mark in swd.Market.objects:
self.markets.append(mark)
for m in range(0, len(self.markets)):
self.marketList.insert(m, self.markets[m].title)
def OpenMarketWizard(self):
mw = MarketWizard.MarketWizard()
mw.RunMarketWizard(self.root)
def market_selected(self, event):
print("Market Selected")
selection = self.marketList.curselection()
selectedMarket = ",".join([self.marketList.get(i) for i in selection])
self.PopulateJobs(selectedMarket)
def PopulateJobs(self, market):
self.jobs.clear()
self.jobsList.delete(0, END)
self.GetJobs(market)
for j in range(0, len(self.jobs)):
self.jobsList.insert(j, self.jobs[j].jobName)
def GetJobs(self, market):
marketJobs = []
jobs = []
if market =="":
return
for j in swd.Job.objects:
jobs.append(j)
print("Market = {0}".format(market))
for i in jobs:
if i.market == market:
self.jobs.append(i)
def job_selected(self, event):
print("Job Selected")
selection = self.jobsList.curselection()
selectedJob = ",".join([self.jobsList.get(i) for i in selection])
print("The selected job is: {0}".format(selectedJob))
def PopulateMarkets(self):
self.marketList.destroy()
for m in range(0, len(self.markets)):
marketList.insert(m, self.markets[m].title)
def OpenJobWizard(self):
jw = JobWizard.JobWizard()
jw.RunJobWizard(self.root)
#We need to pack everything that belongs in our tabbed function.
def marketListDisplay(self, show):
if show == True:
self.marketList.pack(fill="both")
self.UpdateMarkets()
self.newButton=Button(self.marketFrame, text="Open Market Wizard", command=lambda:self.OpenMarketWizard())
self.newButton.pack()
self.refreshButton=Button(self.marketFrame, text="Refresh Markets", command=lambda:self.UpdateMarkets())
self.refreshButton.pack()
else:
self.marketList.forget_pack()
def jobsListDisplay(self, show):
if show==True:
self.jobsList.pack()
self.newButton = Button(self.jobsFrame, text="Create New Job / Open Job Wizard", command=lambda:self.OpenJobWizard())
self.newButton.pack()
else:
self.jobsList.forget_pack()
def polesListDisplay(self, show):
if show==True:
self.polesList.pack()
else:
self.polesList.forget_pack()
#Query, why not put them all in a function?
sp = SpanWalker()
sp.marketListDisplay(True)
sp.polesListDisplay(True)
sp.jobsListDisplay(True)
The answer was given by acw1668. I just added exportselection=0 during the creation process for the listboxes, and it worked perfectly.
Sorry for the confusion, y'all, I've never really asked for help on here before.
Thanks for the help!

Python Tkinker, checkbox append string value to list?

The following code allows me to add/remove France/Italy from a list when clicked on a tkinter checkbox. Rather than passing 0 or 1 to the Add_France function, is it possible to pass through a string "France"? Thanks
My_list = []
root = Tk()
Country_Variable1 = tkinter.IntVar()
Country_Variable2 = tkinter.IntVar()
def Add_France():
if Country_Variable1.get() == 1:
My_list.append("France")
if Country_Variable1.get() == 0:
My_list.remove("France")
print (My_list)
def Add_Italy():
if Country_Variable2.get() == 1:
My_list.append("Italy")
if Country_Variable2.get() == 0:
My_list.remove("Italy")
print (My_list)
check1 = Checkbutton(root, text='France',variable=Country_Variable1, onvalue=1, offvalue=0, command=Add_France)
check2 = Checkbutton(root, text='Italy',variable=Country_Variable2, onvalue=1, offvalue=0, command=Add_Italy)
check1.pack()
check2.pack()
root.mainloop()
You have control over onvalue and offvalue with Checkbutton, so you can play around with the value that it returns in each state. In the below example, you can see how to make checkbutton based on inputs inside the list(assuming it is a list of strings of characters always):
from tkinter import *
root = Tk()
def process(var,text):
try:
val = int(var.get()) # If not selected it will give 0 as int, which will trigger `else` block
except ValueError:
val = var.get()
if val: # if val is not empty, ie, if val is any selected value
slct_ctry_lst.append(val)
else: # if val is 0
slct_ctry_lst.remove(text) # Remove the corresponding text from the list
print(slct_ctry_lst)
slct_ctry_lst = []
countries = ['France','Italy','China','Russia','India']
for idx,i in enumerate(countries):
var = StringVar(value=" ")
Checkbutton(root,text=i,variable=var,command=lambda i=i,var=var: process(var,i),onvalue=i).grid(row=0,column=idx)
root.mainloop()
Thought it would be easier, but maybe not suitable, with ttk.Combobox:
def process(e):
val = e.widget.get()
if val in slct_ctry_lst: # If val present inside the list
slct_ctry_lst.remove(val) # Remove it
else:
slct_ctry_lst.append(val)
print(slct_ctry_lst)
slct_ctry_lst = []
countries = ['France','Italy','China','Russia','India']
cbox = ttk.Combobox(root,values=countries,state='readonly')
cbox.pack(padx=10,pady=10)
cbox.set('Select a country')
cbox.bind('<<ComboboxSelected>>',process)
If the objective here is to make the code reusable for multiple countries, here is initial idea, this can be improved further.
Basically, I am using lambda expression here to the callback command function and passing the country name.
Created a separate function to create a checkbox which is generic for all the countries.
Now to add a new country to the list all you need to do is add an element to the available_country_list
You can make available_country_list global or can pass that as an argument to the list_countries function.
import tkinter
from tkinter import Checkbutton
selected_country_list = []
def select_country(country_name, variable):
global selected_country_list
print(variable)
if variable.get() == 1:
selected_country_list.append(country_name)
elif variable.get() == 0:
selected_country_list.remove(country_name)
print (selected_country_list)
def create_checkbox(parent, country_name):
country_var = tkinter.IntVar()
country_check = Checkbutton(parent, text=country_name, variable=country_var,
onvalue=1, offvalue=0,
command=lambda: select_country(country_name, country_var))
country_check.pack()
def list_countries(root):
available_country_list = ["France", "Italy"]
for country in available_country_list:
create_checkbox(root, country)
def load(root):
# Create list of country checkboxes
list_countries(root)
def start():
root = tkinter.Tk()
root.after(100, load, root) # Enable lazy loading of your components
root.mainloop()
if __name__ == '__main__':
start()

Tkinter Async Error...without multithreading?

I've recently needed to do some GUI work with Python and stumbled on Tkinter. For the most part, I like it; it's clean and mostly intuitive and brief. There is, however, one little sticking point for me: sometimes it crashes out of nowhere. The program will run normally five times in a row and then the sixth time, halfway through it will freeze and I will get the error
Tcl_AsyncDelete: async handler deleted by the wrong thread
My efforts to find a solution to this problem, both on this website and others, all have to do with multithreading, but I don't use multiple threads. Not explicitly, anyway. I suspect the fact that I have a timer in the GUI is to blame, but I have not been able to figure out why the error pops up, or indeed why it is so infrequent.
What follows is the shortest tkinter program I've written. This happens in all of them, so I suspect the problem will be easiest to see here. Any and all help is appreciated, just don't point me toward another solution without telling me how it applies to my code, because I assure you, I have looked at it, and it either doesn't apply or I didn't understand how it did. I don't mind having missed the answer, but I'm new to tkinter (and to multithreading in general) so I might need it told more explicitly.
This code is for a simulation of a game show I saw online. Most of it can probably be safely ignored, but I'm pasting it all here because I don't know where the error is from.
import re
from tkinter import Tk, Frame, DISABLED, Button, Label, font, NORMAL, ttk
from random import choice
import winsound
class Question:
def __init__(self, t, a, o):
self.text = t.strip().capitalize() + "?"
self.answer = a.strip().title()
self.options = o
self.firstIsRight = self.answer.lower() == self.options[0].lower()
assert self.firstIsRight or self.answer.lower() == self.options[1].lower(), self
def __eq__(self, other):
return self.text == other.text
def __repr__(self):
return "{1} or {2}, {0}".format(self.text, self.options[0], self.options[1])
class Application(Frame):
def __init__(self, master=None):
self.setup()
Frame.__init__(self, master)
self.grid()
self.customFont = font.Font(family="Times New Roman", size=30)
self.createWidgets()
def setup(self):
self.questions = []
with open("twentyone.txt",'r') as file:
for line in file:
groups = re.split("[,\.\?]",line)
answers = re.split(" or ",groups[0])
self.questions.append(Question(groups[1], groups[2], answers))
def createWidgets(self):
self.gamePanel = Frame(self)
self.gamePanel.grid(column=0,row=0)
self.displayPanel = Frame(self)
self.displayPanel.grid(column=0,row=1)
self.buttonPanel = Frame(self)
self.buttonPanel.grid(column=0,row=2)
self.QUIT = Button(self.buttonPanel,text="QUIT",font=self.customFont,command=self.quit)
self.QUIT.grid(row=0,column=2)
self.BEGIN = Button(self.buttonPanel, text="BEGIN",font=self.customFont, command = self.begin)
self.BEGIN.grid(row=0,column=0)
self.STOP = Button(self.buttonPanel, text="STOP",font=self.customFont, command = self.stop)
self.STOP.grid(row=0,column=1)
self.STOP["state"] = DISABLED
self.TITLE = Label(self.gamePanel,text="21 Questions Wrong",font=self.customFont,bg="Black",fg="White")
self.TITLE.grid(columnspan=2)
self.questionText = Label(self.gamePanel,text="Questions go here",font=self.customFont,wraplength=400)
self.questionText.grid(row=1,columnspan=2)
self.leftChoice = Button(self.gamePanel,text="Option 1",font=self.customFont)
self.leftChoice.grid(row=2,column=0)
self.rightChoice = Button(self.gamePanel,text="Option 2",font=self.customFont)
self.rightChoice.grid(row=2,column=1)
self.timerText = Label(self.displayPanel, text="150",font=self.customFont)
self.timerText.grid(row=0)
self.progress = ttk.Progressbar(self.displayPanel, length=100,maximum=22)
self.progress.grid(row=0,column=1,padx=10)
def begin(self):
self.timer(250)
self.asked = []
self.STOP["state"] = NORMAL
self.leftChoice["state"] = NORMAL
self.rightChoice["state"] = NORMAL
self.restart = False
self.askNewQuestion()
def askNewQuestion(self):
if self.restart:
self.currentQuestion = self.asked[int(self.progress["value"])]
else:
self.currentQuestion = choice([i for i in self.questions if i not in self.asked])
self.asked.append(self.currentQuestion)
self.questionDisplay()
def questionDisplay(self):
self.questionText["text"] = self.currentQuestion.text
self.leftChoice["text"] = self.currentQuestion.options[0]
self.rightChoice["text"] = self.currentQuestion.options[1]
if self.currentQuestion.firstIsRight:
self.leftChoice["command"] = self.correct
self.rightChoice["command"] = self.wrong
else:
self.leftChoice["command"] = self.wrong
self.rightChoice["command"] = self.correct
def correct(self):
self.progress.step()
if self.progress["value"] >= 21:
self.gameOver(True, 21)
else:
if self.progress["value"] >= len(self.asked):
self.restart = False
self.askNewQuestion()
def wrong(self):
self.restart = True
self.progress["value"] = 0
winsound.Beep(750,700)
self.askNewQuestion()
def stop(self):
self.after_cancel(self.timerAfter)
self.BEGIN["state"] = NORMAL
self.STOP["state"] = DISABLED
def gameOver(self, success, longest):
self.after_cancel(self.timerAfter)
self.BEGIN["state"] = NORMAL
self.STOP["state"] = DISABLED
self.questionText["text"] = "Congratulations!" if success else "Too Bad!"
self.leftChoice["text"] = "Game"
self.leftChoice["state"] = DISABLED
self.rightChoice["text"] = "Over"
self.rightChoice["state"] = DISABLED
self.showPoints(success, longest)
def showPoints(self, s, l):
if s:
timeTaken = max(0, 100-int(self.timerText["text"]))
print("You scored {0} points".format(1000-10*timeTaken))
else:
print("You scored no points")
def timer(self, time):
self.BEGIN["state"] = DISABLED
self.STOP["state"] = NORMAL
self.runTimer(time)
def runTimer(self, current=None, resume=False):
if current is not None:
self.current = current
self.timerText["text"] = self.current
if self.current == 0:
self.gameOver(False, len(self.asked)-1)
else:
self.current -= 1
self.timerAfter = self.after(1000,self.runTimer)
root = Tk()
app = Application(master=root)
app.master.title("Score Calculator!")
app.anchor("center")
root.mainloop()
root.destroy()

How do you wait for an user to click a tkinter button with threads

I am currently working on a projet similar to tic tac toe (with an AI)
And I did a GUI to allow the user to play with the bot
But the program doesn't wait for the player to choose and instead it crash because it doesn't have value
So I searcher about threads, tried for a long time can't achieve to figure out how it works
I did some tests (bellow) that my archly resemble what I need to do with my code
But it doesn't work neither
Do someone have an answer ?
import threading
import tkinter as tk
windo = tk.Tk()
windo.title("Morpion")
windo.resizable(width=tk.FALSE, height=tk.FALSE)
class Player:
def __init__(self,name):
self.name = name
self.choice = None
def Change_Var(x):
print(P.choice)
P.choice = x
print(P.choice)
play_event.set()
def boucle():
i = 0
while not play_event.isSet() and i < 3000:
print(i)
i += 1
P = Player("Deltix")
start = tk.Button(height=2,width=8, text = "Start",command = lambda x = 0 : Change_Var(x))
start.grid(column = 2, row = 3,pady = 5)
play_event = threading.Event()
threading.Thread(target = windo.mainloop()).start
threading.Thread(target = boucle()).start```
You don't need to use threads, just use events.
Say you wanted to bind a function to the enter key.
This would be your code:
canvas = Canvas(master)
def clicked(event):
print("Enter was pressed")
canvas.bind("<Return>", clicked)
You can read more about tkinter events here.
Hopefully this helps!

Categories

Resources