I'm doing my first big project which is a quiz. I am stuck on trying to limit the time the user has to answer a question. I've searched and the only option that seems to work is using a timer thread. I'm not familiar with threading or any slightly advanced tkInter at all so I'm all ears.
def revisionMode(question):
inputAnswer = StringVar()
#-----Creation & placement of buttons and labels
qLabel = Label(screen1, text = question.prompt[0]
qLabel.grid(row = 6, column = 2)
answerBox = Entry(screen1, textvariable = inputAnswer)
answerBox.grid(column = 2, row = 10)
t = Timer(7.0, nextQuestion, args=(False, question.difficulty), kwargs=None)
t.start()
#-----The button that will trigger the validation of the answer
Button(screen1, text = "Submit", command = lambda: checkAnswer(question)).grid(column = 3, row = 9)
The error I get from that is that is:
RuntimeError: main thread is not in main loop.
From my understanding and googling, tkinter and threading don't work together very well and I've seen solutions using Queues.
You don't need a timer thread for something this simple. Tkinter widgets have a method named after which can be used to run commands in the future.
For example, to call nextQuestion after 7 seconds, you would do this:
screen1.after(7000, nextQuestion, False, question.difficulty)
If you want to cancel the timer, save the return value and use it in a call to after_cancel:
after_id = screen1.after(7000, nextQuestion, False, question.difficulty)
...
screen1.after_cancel(after_id)
Related
I am currently struggling trying to use the panel library in Python, in order to build an interactive dashboard to analyze and display CSV data. My current goal is to let the user enter an initial and a final date, which will be used to filter a DataFrame once a button is pressed. However, whenever I press the button, the on_click function is not completely executed before the script stops running. The code snippet is the following:
import panel as pn
pn.extension()
def acquire_data(dateBeginning, dateEnd):
eventDF = pd.read_csv('multi.csv')
eventDF['Date']= pd.to_datetime(eventDF['Date'])
dateDF = eventDF[eventDF.upvotes > 8]
print(eventDF)
def register_dates(event, save=True):
dateBeginning = date1Picker.value
dateEnd = date2Picker.value
if dateBeginning < dateEnd:
text = pn.widgets.StaticText(name='Static Text', value='A string')
spinner = pn.indicators.LoadingSpinner(width=50, height=50, value=True, color='info', bgcolor='light')
layout = pn.Column(text, spinner, align='center')
layout.app()
print('getting in')
acquire_data(dateBeginning, dateEnd)
print('getting out')
spinner.value = False
else:
print('Not working')
#pn.pane.Alert('## Alert\nThis is a warning!')
return save
date1Picker = pn.widgets.DatePicker(name='Date Initiale', margin=25)
date2Picker = pn.widgets.DatePicker(name='Date Finale', margin=25)
button = pn.widgets.Button(name="Analyse", button_type='primary', margin=(25, 0, 20, 200), width=200)
button.on_click(register_dates)
dateLayout = pn.Row(date1Picker, date2Picker)
layout = pn.Column(dateLayout, button, width=200, align='center')
layout.app()
I was also aiming at having the first layout be replaced by the one with the spinner and the text once the button is pressed, but I haven't found anything in the doc mentioning how to do so. If anyone could give me a hint regarding these issues, that would really help me!
In def acquire_data(dateBeginning, dateEnd):
pd.read_csv('multi.csv'), pd.to_datetime(eventDF['Date'])
For start, in this function I think you forgot to import panda and your app just crash.
add: import pandas as pd
Ex:
import panel as pn
import pandas as pd
I have a script that continuously updates numbers on a text file in the same directory. I made a GUI using tkinter to display these numbers. I'm having a hard time getting the GUI to update the numbers in real time. It will display the numbers as they were when the GUI first started, but will not update the display as the text file changes. Here's some basic code to demonstrate:
def read_files(file, line):
old = open(f'{file}.txt', 'r').readlines()[line]
new = old.replace('\n', '')
return new
number = read_files('Statistics', 0)
number_label = Label(frame1, text=f'{number}')
number_label.grid(row=0, column=0)
The above code shows the number from the text file as it was when the GUI first opened. However, it does not update the number as its value in the text file changes. I did some reading around and also tried the following:
def read_files(file, line):
old = open(f'{file}.txt', 'r').readlines()[line]
new = old.replace('\n', '')
return new
number = read_files('Statistics', 0)
number_label = StringVar()
number_label.set(number)
number_display = Label(frame1, text=f'{number_label.get()}')
number_display.grid(row=0, column=0)
This has the same effect. It shows the value retrieved from the text file at the moment the GUI was opened, but does not update it as the text file is updated. Any help is appreciated.
Since there is no complete code, take a look at this example:
from tkinter import *
root = Tk()
def update_gui():
number = read_files('Statistics', 0) # Call the function
number_display.config(text=number) # Change the text of the label, also same as text=f'{number}'
root.after(1000,update_gui) # Repeat this function every 1 second
def read_files(file, line):
old = open(f'{file}.txt', 'r').readlines()[line]
new = old.replace('\n', '')
return new
number_display = Label(root) # Empty label to update text later
number_display.grid(row=0, column=0)
update_gui() # Initial call to the function
root.mainloop()
Here the label is created outside the function but the text is not given, but inside the function we are repeating the function every 1 second, with after, and changing the text of the label with config method. I have avoided the use of StringVar() as it's of no use here, you can just store it in a normal variable and use it.
Here is a plagiarized look at how after should be used:
def after(self, ms, func=None, *args):
"""Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel."""
I am fooling around with GUI programming using Tkinter on python, with the goal being a GUI app which will allow a user to add a task or delete (to be saved to a txt file).
Right now, I am just trying to figure out the basics of adding/removing checkbuttons.
The issue I am having is that when I run the script, the check boxes are not aligned with the text for them. The picture below shows the issue.
The code used to pull tasks from a txt file and add them as checkbuttons is as follows:
tasks = []
with open(r"C:\Users\patte\Desktop\tasks.txt") as file:
tasks = file.readlines()
row = 2
for task in tasks:
self.task_checkbox = Checkbutton(self.master,text=task,font=("Arial",12),relief='groove')
self.task_checkbox.grid(row=row,column=0,sticky=W)
row+=1
Note that row is initialized to 2 as the title and entry bar take up rows 0 and 1. I have tried changing fonts, etc., but the result stays the same.
Any sort of advice or criticism is greatly appreciated.
The full code is as follows:
from tkinter import *
class ToDo():
def __init__(self,master):
self.master = master # Geometry and layout when initialized
master.geometry("800x800")
master.title("TO DO")
self.gui_title = Label(self.master,text="Tasks:",font = ("Arial Bold",26))
self.gui_title.grid(row=0,column=0,sticky=W)
self.task_entry_label = Label(self.master,text="Enter Task:",font = ("Arial",12))
self.task_entry_label.grid(row=2,column=0,sticky=W)
self.task_entry = Entry(self.master)
self.task_entry.grid(row=2,column=1,sticky=W)
self.task_entry.focus_set()
tasks = []
with open(r"C:\Users\patte\Desktop\tasks.txt") as file:
tasks = file.readlines()
row = 3
for task in tasks:
self.task_checkbox = Checkbutton(self.master,text=task,font=("Arial",12),relief='groove')
self.task_checkbox.grid(row=row,column=0,sticky=W)
row+=1
self.QuitButton = Button(self.master,text="Quit",font=("Arial Bold",12),command = lambda: self.master.quit())
self.QuitButton.grid(sticky=SW,row=7)
root = Tk()
Tasks = ToDo(root)
root.mainloop()
Thanks!
The title might be a little confusing, so i will describe my question more.
I making a little program that will assist me with studying Chinese, just for myself. This will aid me with coding and in same time with studying.
I encounter a problem with getting the text variable from my button, without function the code work like wanted. But when trying to get random text that shown on the button it cause me a problem, because text doesn't come. All i need it, when button is pressed function check if input is the same as character and give correct/incorrect notice.
I little new to coding, so it can be simple matter, but still appreciate any help.
The code:
#========== Imports ===========#
from tkinter import *
from tkinter import messagebox
import random
#========== Parameters ==========#
CN = Tk()
CN.title("Chinese Test")
CNW = ["爱","八","爸爸","杯子","北京","本","不客气","不","菜","茶","吃","出租车","打电话",
"大","的","点","电脑","电视","电影","东西","都","读","对不起","多","多少","儿子",
"二","饭店","飞机","分钟","高兴","个","工作","汉语","好","号","喝","和","很","后面","回","会","几","家","叫","今天"]
Cword = ""
Cent = StringVar()
def butPress():
global Cword
if (B0.text==Cword): #wrong way to get text
messageText = "Correct"
else:
messageText = "Incorrect"
CNEntry = Entry(CN,textvariable = Cent).grid(row = 0, column = 1, columnspan = 8)
B0 = Button(CN, text = random.choice(CNW),command = lambda:butPress,bd = 3, width = 5, height = 3).grid(row = 6, column = 4, padx = 10, pady = 10)
#========== Pack ==========#
CN.mainloop( )
There's a few things.
First of all, command = lambda:butPress doesn't work. Use command = butPress. You should only use a lambda when you need to pass parameters (e.g. command = lambda:butPress(parameter)), which you don't.
Then there's B0.text. Because you do
B0 = Button(...).grid(...)
B0 is None, because that is what grid() returns. Change it to
B0 = Button(...)
B0.grid(...)
This way B0 is a Button object. To get the current text of it you can't use B0.text, you have to use B0['text'].
You then compare the text to Cword, which is '' and never changes. If you want to compare it to the entered text in the Entry use CNEntry.get() (after again putting grid on a separate line).
Here is the code I'm working with:
import sys
from tkinter import *
from random import choice
def motiv():
motiv1 = mLabel = Label(text='Waste not, want not', fg="red").pack()
motiv2 = mLabel = Label(text='Sticks and stones', fg="red").pack()
motiv3 = mLabel = Label(text='Keep holding on', fg="red").pack()
motiv4 = mLabel = Label(text='Hold On, Pain Ends', fg="red").pack()
ranMotiv = [motiv1, motiv2, motiv3, motiv4]
print (choice(ranMotiv))
mGui = Tk()
mGui.geometry('450x450+500+150')
mGui.title('RMM')
mLabel = Label(text='Welcome to the Random Motivation Machine', fg="red").pack()
mButton = Button(text = "Click for Some Motivation", command = motiv)
mButton.pack()
mGui.mainloop()
There are no errors, but it keeps printing out all of those texts at the same time, when I'm wanting it to only print out only one of them at random.
My goal is to have someone press the button and out pops a random phrase in the GUI window.
So someone presses the button and only one of any of the four text phrases comes out on the window:
1.Waste not, want not.
2.Sticks and stones
3.Keep holding on.
4.Hold on, Pain Ends.
I believe my troubles are arising from this area right here:
ranMotiv = [motiv1, motiv2, motiv3, motiv4]
print (choice(ranMotiv))
Does anyone have any ideas? This is just a very small pet project of mine. I've only been using Python for less than a few months so I'm not very astute. I'm running Python 3.2.5 by the way. Thank you all in advance.
I originally posted this as a comment, but it turned out to be the answer, so I'm reposting it here:
The problem is that Label(text='Waste not, want not', fg="red").pack() packs the label right away. Doing this with all labels causes them to be packed. It doesn't matter if you call random.choice later because the labels have already been packed into your GUI.
If you want to create a random label from a pool of labels, What you want to do is this:
def motiv():
myLabels = ['Waste not, want not', 'Sticks and stones', 'Keep holding on', 'Hold On, Pain Ends']
chosenLabel = random.choice(myLabels)
randMotiv = Label(text=chosenLabel, fg="red").pack()
How about this:
from tkinter import *
from random import choice
# Place the messages outside of the function so that they are not
# re-created each time the button is pressed
messages = ['Waste not, want not', 'Sticks and stones',
'Keep holding on', 'Hold On, Pain Ends']
def motiv():
# Just update the text of the Label instead of create a whole new one
message["text"] = choice(messages)
mGui = Tk()
mGui.geometry('450x450+500+150')
mGui.title('RMM')
mLabel = Label(text='Welcome to the Random Motivation Machine', fg="red").pack()
mButton = Button(text = "Click for Some Motivation", command = motiv)
mButton.pack()
# Make a Label to hold the messages that will be updated with each click of the button
message = Label(fg="red")
message.pack()
mGui.mainloop()
Instead of just tacking a new message to the bottom of the GUI, this method is cleaner and just updates the text of the message. It also fixes the problem of messages running off of the GUI (you can see this by clicking the button like 30 times).