How to run a function from a button based GUI window? - python

I'm trying to make a menu driven program that has GUI capability. For the sake of this post I edited it down so the menu has 2 options, to quit and to calculate an average of a set of numbers. I ran this before I used the GUI code and it worked perfectly.
The issue is that when I click "Average", it runs the function and allows 1 input but then I can't enter another number. Same issue with other functions I had. They won't run past the first input and then the Quit button also won't quit.
Feel free to ask any questions in the comments if I wasn't good at describing what I needed. It's my first day using GUI so sorry if anything looks weird, I'm trying to teach myself but got stuck here. End goal is to have the whole program run in a GUI environment but I figured starting with the menu would be the best place to start.
from tkinter import *
import tkinter as tk
def average():
print("Enter test scores to get the average of. Type '-99' to quit.")
num = 1
count = 0
total = 0
while num != -99:
num = int(input("Enter numbers: "))
total += num
count += 1
average = (total + 99) / (count-1)
print("The average is: ", average)
print("-----------")
main()
def main():
r = tk.Tk()
r.title('Number games')
button1 = tk.Button(r, activebackground = "blue", bg = "light blue", text='Average', width=25, command= average)
button4 = tk.Button(r, activebackground = "blue", bg = "light blue", text='Quit', width=25, command=r.destroy)
button1.pack()
button4.pack()
r.mainloop()
main()

You don't need to call main() from inside average(). When I delete that line, your program works exactly as expected for me (debian 9, python3).
Ps. Don't use import *.

Globalize the variable r and declare r.destroy() in another function above main function. Note from tkinter import * is enough no need to import tkinter again with an alias.

Related

Random Number Generator with Tkinter module - Unable to clear old label data and need to populate it with the new data on click

This morning I wrote a simple random number generator (using Tkinter module), which generates a random number from 1-100. With my below code, it is functional and not generating any errors, however when I roll the next number, if it is a single digit vs a double digit.. The single digit will have overlapping text on the next generated label.
Essentially what I am aiming to do is either use configure() or destroy() to clear the old number / text that was generated on click, and update it with the new text / number data. I want this to occur after every click so I do not get any overlapping or funky looking output.
I tried researching on stackoverflow, and other people's code was confusing to me and didn't really apply to what I wrote. Searched through the Python Docs and it gave me the commands to utilize, but not how to utilize it in my example program... it was quite confusing. I also googled a bunch and watched several YouTube videos... and I still can't solve this supposedly simple task. Any help is much needed and appreciated! Expecting it to be cleared and then populated with the new text and rolled number. Thanks!
# Creating a Program to roll a # between 1-100.
import random
from tkinter import *
import tkinter as tk
root = tk.Tk()
def action():
roll = random.randint(1, 100)
result = tk.Label(text=f"You rolled a {roll}! ", font='Helsinki, 30')
result.place(x=300, y=220)
root.geometry("800x600")
title = root.title("Random Roll Generator")
description = tk.Label(text="Welcome you degenerate! Click the button below to roll a random number from 1-100.", font='Helsinki, 20')
description.place(x=20, y=100)
roll_button = tk.Button(text="Roll", padx=20, pady=0, command=action)
roll_button.place(x=360, y=520)
root.mainloop()
My solution is to create the label first, and then use the config command to update its text, without the need to create and package a new one.
Note that the fstring allows formatting, that is, you can always leave your number with 3 digits, avoiding unnecessary movements in the layout
# Creating a Program to roll a # between 1-100.
import random
import tkinter as tk
root = tk.Tk()
root.geometry('300x300')
rand_label = tk.Label(master=root, text='Click to roll New randon number.')
rand_label.pack()
def new_rng():
rand_label.config(text=f'New randon number: {random.randint(0, 100):03}')
rand_button = tk.Button(master=root, text='Roll', command=new_rng)
rand_button.pack()
root.mainloop()
You are placing a new label each time you roll with the current script, and that is why sometimes you see the overlapping label.
Use a string variable and update the variable.
# Creating a Program to roll a # between 1-100.
import random
from tkinter import *
import tkinter as tk
root = tk.Tk()
def action():
roll = random.randint(1, 100)
result_text.set(f"You rolled a {roll}! ")
root.geometry("800x600")
title = root.title("Random Roll Generator")
description = tk.Label(text="Welcome you degenerate! Click the button below to roll a random number from 1-100.", font='Helsinki, 20')
description.place(x=20, y=100)
result_text = StringVar("") # string variable to store result
result = tk.Label(textvariable = result_text, font='Helsinki, 30')
result.place(x=300, y=220)
roll_button = tk.Button(text="Roll", padx=20, pady=0, command=action)
roll_button.place(x=360, y=520)
root.mainloop()
The correct way to used widget.config in line 11. No needed to used StringVar(). Do not use this from tkinter import * is not widely used.
Code:
import random
import tkinter as tk
root = tk.Tk()
def action():
roll = random.randint(1, 100)
result.config(text=f"You rolled a {roll}! ")
root.geometry("800x600")
title = root.title("Random Roll Generator")
description = tk.Label(text="Welcome you degenerate! Click the button below to roll a random number from 1-100.", font='Helsinki, 20')
description.place(x=20, y=100)
result = tk.Label( font='Helsinki, 30')
result.place(x=300, y=220)
roll_button = tk.Button(text="Roll", padx=20, pady=0, command=action)
roll_button.place(x=360, y=520)
root.mainloop()
Screenshot:

how do I make Python tkinter message box show a number from elsewhere in the script

Will preface this by saying i am very new to python and coding in general,
I followed a tutorial on how to make a countdown timer, and have managed to code a button that measures the amount of times it has been clicked by mashing my code for the button with a post i found on a forum, the count down timer displays a "Times up" message box at the end, what i want to do is have it also display the amount of times the button is clicked in the allotted time. I've tried calling the global count from within the countdown timer and reusing the line that displays the count in the GUI but this seems to break it and doing it without it as displayed here simply shows the string as it is written, any help or guidance is appreciated
'''
import time
from tkinter import *
from tkinter import messagebox
f = ("Arial",24)
root = Tk()
root.title("Click Counter")
root.config(bg='#345')
root.state('zoomed') #opens as maximised, negating the need for the 'Geometry' command
count = 0
def clicked():
global count
count = count + 1
myLabel.configure(text=f'Button was clicked {count} times!!!')
hour=StringVar()
minute=StringVar()
second=StringVar()
hour.set("00")
minute.set("00")
second.set("10")
hour_tf= Entry(
root,
width=3,
font=f,
textvariable=hour
)
hour_tf.place(x=80,y=20)
mins_tf= Entry(
root,
width=3,
font=f,
textvariable=minute)
mins_tf.place(x=130,y=20)
sec_tf = Entry(
root,
width=3,
font=f,
textvariable=second)
sec_tf .place(x=180,y=20)
TheButton = Button(root, height= 30, width=100, bg="light blue", text="Click For Your Life", command=clicked) #tells the button to call the function when clicked
TheButton.pack()
myLabel = Label(root)
myLabel.pack()
def startCountdown():
try:
userinput = int(hour.get())*3600 + int(minute.get())*60 + int(second.get())
except:
messagebox.showwarning('', 'Invalid Input!')
while userinput >-1:
mins,secs = divmod(userinput,60)
hours=0
if mins >60:
hours, mins = divmod(mins, 60)
hour.set("{0:2d}".format(hours))
minute.set("{0:2d}".format(mins))
second.set("{0:2d}".format(secs))
root.update()
time.sleep(1)
if (userinput == 0):
messagebox.showinfo("Time's Up!!", "you clicked (count) times")
userinput -= 1
start_btn = Button(
root,
text='START',
bd='5',
command= startCountdown
)
start_btn.place(x = 120,y = 120)
root.mainloop()
'''
You need to use an f-String with "{}", like you did in the clicked function:
if (userinput == 0):
messagebox.showinfo(f"Time's Up!! you clicked {count} times")
A kind user provided the correct answer but has seen fit to delete their comment so in case anyone like me is trawling through old posts looking for help here is what worked:
To have your messagebox display the number of clicks (in this case
count), you can simply use
messagebox.showinfo("Time's Up!!", "you clicked " + str(count) + " times")
instead of
messagebox.showinfo("Time's Up!!", "you clicked (count) times")
Your attempt won't work, because you included count inside the
quotation marks, meaning that Python will completely ignore the fact
that count is also a variable and just print it as a string. There are
a lot of ways you can format strings to include variables etc. Check
out here for a good tutorial/explanation.

Fast changing labels in Tkinter?

I would like to display some numbers, as fast as possible in Tkinter. The Program, I am trying to do, gets many numbers send and should show those.
Here is an similar environment, where tinter has to change a label very quickly.
from tkinter import *
import time
window = Tk()
lbl13 = Label(window, text="-")
lbl13.grid(column=0, row=0)
x = 0
while 1:
lbl13.config(text = str(x))
time.sleep(2)
x +=1
window.mainloop()
The Tkinter window doesn't even open on my computer. Is that because i have too weak hardware? What could I change that this Program also runs on my Computer. Thank you for every answer!
The infinite while loop will keep the program from getting to the line where you call window.mainloop(). You should call window.update() repeatedly instead of window.mainloop() at the end:
from tkinter import *
import time
window = Tk()
lbl13 = Label(window, text="-")
lbl13.grid(column=0, row=0)
x = 0
while 1:
lbl13.config(text = str(x))
window.update()
x +=1
Using after and a proper mainloop is probably a more more flexible way to achieve what you want; it is also reusable in different contexts, and can be used in an application that does more than trivially increment a number on a label:
maybe something like this:
import tkinter as tk
if __name__ == '__main__':
def increment():
var.set(var.get() + 1)
label.after(1, increment)
window = tk.Tk()
var = tk.IntVar(0)
label = tk.Label(window, textvariable=var)
label.pack()
increment()
window.mainloop()

Problem with calling functions with tkinter

from tkinter import *
from random import *
root = Tk()
#A function to create the turn for the current player. The current player isnt in this code as it is not important
def turn():
window = Tk()
dice = Button(window, text="Roll the dice!", bg= "white", command=lambda:diceAction(window))
dice.pack()
window.mainloop()
#a function to simulate a dice. It kills the function turn.
def diceAction(window):
result = Tk()
y = randint(1, 6)
quitButton = Button(result, text="Ok!", bg="white", command=result.destroy)
quitButton.pack()
window.destroy()
result.mainloop()
#A function to create the playing field and to start the game
def main():
label1 = Button(root, text="hi", bg="black")
label1.pack()
while 1:
turn()
print("Hi")
turn()
main()
root.mainloop()
My problem is that the code in the while function after the first turn() the code isnt executed until i close the root window(which i dont want because it represents the playing field). You can copy this code and execute it yourself if you want.
I have no idea what causes this and havent found anything online. Sorry for the long code but i wrote it so that it is executeable.
I don't know why this particular problem is occurring, but there are a couple of things in your code that are considered bad practice.
Instead of creating multiple instances of Tk(), you should use Toplevel widgets for any pop-up windows needed. Also, it's better to use root.mainloop() to run the program rather than a while loop.
I've made some edits to your code so that it uses a Toplevel widget and discards of the while loop.
from tkinter import *
from random import *
#A function to create the turn for the current player. The current player isnt in this code as it is not important
def turn(prev=None):
# destroy the previous turn
if prev:
prev.destroy()
# pop up with dice
window = Toplevel()
dice = Button(window, text="Roll the dice!", bg= "white")
dice.config(command=lambda b=dice, w=window:diceAction(b, w))
dice.pack()
#a function to simulate a dice, reconfigures the pop-up
def diceAction(button, window):
# roll dice
y = randint(1, 6)
# use dice result here?
print(y)
# reconfigure button, the command now starts a new turn
button.config(text='ok', command=lambda w=window:turn(prev=w))
root = Tk()
# I hijacked this button to use as a start button
label1 = Button(root, text="hi", bg="black", command=turn)
label1.pack()
root.mainloop()
I don't know if this is what you need, but it functions as the code in the question would if it worked.
Sorry I couldn't help with the cause of the error.

In python, using tkinter, my random numbers only generate once?

Basically, I have this code:
from tkinter import *
from tkinter import messagebox
import random
import string
from tkinter import filedialog
ktwoWin = Tk() #window
qLabel = StringVar()
userAnswer = StringVar()
ktwoWin.withdraw() #hide the ktwoWin window
pass
num1= random.randint(1, 12) #random numbers
num2= random.randint(1, 12)
Answer= num1 * num2
def ktwoOpen():
ktwoWin.deiconify() #show the ktwoWin window
ktwoWin.title("Kindergarten to Grade Two")
ktwoWin.geometry("400x300")
ktwoWin.grid()
askbutton= Button(ktwoWin, text="ask me a question!", command = askquestion, height=3, width=16, bg="blue")
askbutton.grid(column= 0, row= 0)
submitbtn= Button(ktwoWin, text="Submit Answer", command=checkanswer, height=3, width=12, bg= "red")
submitbtn.grid(column=1, row=0)
q=Label(ktwoWin,textvariable=qLabel)
q.grid(column=1, row=1)
q.config(text="text to go here")
qLabel.set("some text")
answerentry= Entry(ktwoWin, textvariable=userAnswer)
answerentry.grid(column=3, row=3)
pass
def askquestion():
qLabel.set("what is" +str(num1) + "x" + str(num2) + "?")
def checkanswer():
useranswer=userAnswer.get()
if int(useranswer) != Answer:
messagebox.showwarning(message="the answer is " + str(Answer))
else:
messagebox.showinfo(message="correct!")
ktwoWin.mainloop
Which, when I run the program, the random numbers I have, will only randomise once, if that makes sense? My question, ultimately, is there a way for me to loop the random number part of the code?
any help is appreciated, thanks:)
It should be pretty straight forward to set up a new question each time one is answered. I'm not an expert at tkinter, so this is fairly general advice.
To start with, set up your two random numbers in the askquestion function, rather than at the top level of your module. Since you need to be able to access the product later, use the global keyword to make sure that the answer is available outside the function's scope (or you could reorganize the code into a class and use member variables).
You may also need to add some extra logic to the end of the checkanswer function to reset the program's state after an answer has been provided (e.g. clear the old question label and remove the old answer from the input box). Depending on how you want the program to play, you might immediately ask a new question, or you might go back to the original starting state.

Categories