My tkinter gui starts to freeze when I click on somewhere else. Is there a way to prevent that?
Here's my code:
#=========================
from tkinter import *
from time import sleep
import random
#=====================
root=Tk()
root.title("Wise Words")
root.geometry("500x180+360+30")
root.resizable(0,0)
root.call("wm", "attributes", ".", "-topmost", "1")
#===================
def display(random):
if random == 1:
return "Be wise today so you don't cry tomorrow"
elif random == 2:
return "Frustration is the result of failed expectations"
elif random == 3:
return "Wishes are possibilities. Dare to make a wish"
if True:
sleep(4)
r=random.randint(1,3)
sentence=display(r)
label.configure(text=str(sentence))
label.update_idletasks()
root.after(5000, display(random))
#==================
def Click(event):
display(random)
#======================
label=Button(root, fg="white", bg="blue", text="Click to start!",
font=("Tahoma", 20, "bold"), width=40, height=4,
wraplength=400)
label.bind("<Button-1>", Click)
label.pack()
#================
root.mainloop()
Note: The label for display is the Button itself, so I name it 'label'.
You're doing several strange things in your code:
Using time.sleep in a Tkinter application
Calling the button a label (there is a Label Tkinter widget)
Binding the left mouse button to a button instead of just giving the button a command
Passing the random module around and expecting it to evaluate to an integer
Returning strings to a button
Using an unconditional branching statement (if True:)
Masking a module name with a parameter name
Expecting the name random to refer to both the random module and a passed argument, at the same time
Making a recursive call in a function that already calls after
Leaving the button bound to a function that already schedules calls to itself with after, allowing you to schedule many calls
Using an if structure to choose a random string instead of using random.choice
Scheduling an after call with the result of a function call (display(random)) instead of the function itself
That's not necessarily a complete list.
The following fixes the above issues.
from tkinter import *
import random
def display():
strings = ("Be wise today so you don't cry tomorrow",
"Frustration is the result of failed expectations",
"Wishes are possibilities. Dare to make a wish")
button.config(text=random.choice(strings))
root.after(5000, display)
def click(event=None):
button.config(command='')
display()
root=Tk()
root.title("Wise Words")
root.geometry("500x180+360+30")
root.resizable(0,0)
root.call("wm", "attributes", ".", "-topmost", "1")
button = Button(root, fg="white", bg="blue", text="Click to start!",
font=("Tahoma", 20, "bold"), width=40, height=4,
wraplength=400, command=click)
button.pack()
root.mainloop()
Related
I have two widgets to work with, a text input, and a button, both are created inside a function. What I want to happen is the user types in their name and then clicks the button to submit the answer. What I want the computer to do, is on the button press it will read whats inside the text and the save it to a variable. Once it saves it, it will print it out.
The code below is bad because it runs through the if statement immediately without the consulting of the button press.
There has to be a simpler solution. Also this may not be PEP 8 or whatever please be patient because I'm new.
import tkinter as tk
from tkinter import Tk, Label, Button
import sys
import time
import random
import threading
from tkinter import *
window = tk.Tk()
window.geometry("300x300")
window.title("GUI")
def start_screen():
reset()
start = tk.Label(window, text="start of game")
start.place(x=110,y=20)
play = Button(window, text= "play", command = start_game)
play.place(x=110,y=50)
helper = Button(window, text="help", command = help_screen)
helper.place(x=110,y=70)
def stuff():
global t
t = True
print(t)
return t
def text_handling():
global t
t = False
reset()#clears the screen
label = Label(window, text='')
question1= "what is your name?"
label.pack()
print_slow(label, question1, 40)#prints out letters slowly
#here is the part I'm having problems with
name = Entry(window)
name.pack()
but = Button(window, text="enter", command= stuff)
but.pack()
print(t)
if t == True:
myPlayer.name = name.get()
print(myPlayer.name)
def start_game():
reset()
bt = tk.Button(window,text="Enter", bg="orange", command =
text_handling)
bt.place(x=100,y=100)
start_screen()
I try to show a status from a called function in real time. However, the messages appears in the GUI all at ones after function is done. What can I do?
Thanks for your help!
from tkinter import *
import time
def sleep():
msgbox.insert(INSERT,"go sleep...\n")
time.sleep(2)
msgbox.insert(INSERT,"... need another 2 sec \n")
time.sleep(2)
msgbox.insert(INSERT,"... that was good\n")
return
root = Tk()
root.minsize(600,400)
button= Button(text="Get sleep", command=sleep)
button.place(x=250, y=100,height=50, width=100)
msgbox =Text(root, height=10, width=60)
msgbox.place(x=20, y=200)
mainloop()
You can use the Tk.update() function to update the window without having to wait for the function to finish:
from tkinter import *
import time
def sleep():
msgbox.insert(INSERT,"go sleep...\n")
root.update()
time.sleep(2)
msgbox.insert(INSERT,"... need another 2 sec \n")
root.update()
time.sleep(2)
msgbox.insert(INSERT,"... that was good\n")
root = Tk()
root.minsize(600,400)
button= Button(text="Get sleep", command=sleep)
button.place(x=250, y=100,height=50, width=100)
msgbox =Text(root, height=10, width=60)
msgbox.place(x=20, y=200)
mainloop()
(also the return is unnecessary, you use it if you want to end a function partway through or return a value from the function).
Let me know if you have any problems :).
Imagine the following simple example:
def doNothing():
sleep(0.5)
barVar.set(10)
sleep(0.5)
barVar.set(20)
sleep(0.5)
barVar.set(30)
mainWindow = Tk()
barVar = DoubleVar()
barVar.set(0)
bar = Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= Button(mainWindow, text='Click', command=doNothing)
button.grid(row=0, column=0)
mainWindow.mainloop()
What I get when I run this, the progressbar is already at 30% when clicking the button, no progress in front of me. Like attached:
What I need: I can see the progress in front of me (not hanging then suddenly 30%)
Update:
I upadted the code according to #Bernhard answer, but still I can not see the progress in front of me. Just a sudden jump of 30% after waiting 1.5 sec
Seocnd Update:
I'm only using sleep here as a simulation for a process that takes time, like connecting over ssh and grabing some info.
Do not use sleep() in tkinter. The entire reason for you problem is sleep() will freeze tkinter until it is done with its count so what you are seeing is a frozen program and when the program is finally released its already set to 30 percent on the next mainloop update.
Instead we need to use Tkinter's built in method called after() as after is specifically for this purpose.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+10)
mainWindow.after(500, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
If you want the bar to appear to move smoothly you will need to speed up the function call and reduce the addition to the DoubbleVar.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+0.5)
mainWindow.after(50, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
Because you are calling the function when the buttion is initialized, you need to loose the '(barVar') in the command=(barVar)). This way you bind the function to the button and don't call it when initializing it.
button= Button(mainWindow, text='Click', command=doNothing)
If you need to pass an argument you need to bypass the calling by using lambda:
button= Button(mainWindow, text='Click', command= lambda: doNothing(barVar))
I think I find the solution.
simply add mainWindow.update() after each progress. So the final code would be:
def doNothing():
sleep(0.5)
barVar.set(10)
mainWindow.update()
sleep(0.5)
barVar.set(20)
mainWindow.update()
sleep(0.5)
barVar.set(30)
mainWindow.update()
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 = Toplevel()
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 = Toplevel()
y = randint(1, 6)
# i do something with this number
quitButton = Button(result, text="Ok!", bg="white", command=lambda: [result.destroy(), window.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()
With this code i basically create a roll the dice simulator. In my actual code i give the function turn() player1/player2(which are class objects) so i can track whose turn it is. Thats why i call turn() 2 times in the while.
The problem is that the code after the first turn() isnt executed(until i manually close the root window which is weird) anymore. At my knowledge this should work.
I open the turn function which opens the diceAction function upon button press. diceAction() gives me the number and kills both windows. Then the second turn() should be called and the process continues until someone wins(which i havent implemented in this code).
The print("Hi") isnt executed either. Am i missing something? You can copy this code and execute it yourself.
The short answer is "Infinite loops and tkinter don't play well together". The long answer is that you never escape window.mainloop(). I don't see a good enough reason that you need to have window.mainloop() and result.mainloop() running to justify the headache of multiple loops in tkinter.
A subjectively better way of doing this is to have the end of the first turn() trigger the start of the next one:
from tkinter import *
from random import *
root = Tk()
global turnCount
turnCount = 0
def turn():
window = Toplevel()
dice = Button(window, text="Roll the dice!", bg="white", command=lambda:diceAction())
dice.pack()
def diceAction():
result = Toplevel()
y = randint(1, 6)
quitButton = Button(result, text="Ok!", bg="white", command=lambda: nextTurn())
quitButton.pack()
def nextTurn():
global turnCount
turnCount = turnCount + 1
for i in root.winfo_children():
if str(type(i)) == "<class 'tkinter.Toplevel'>":
i.destroy()
turn()
def main():
label1 = Button(root, text="hi", bg="black")
label1.pack()
turn()
main()
root.mainloop()
I would recommend attempting to use OOP on a project like this instead of the global that I declared above.
I've been working on a text editor using Tkinter in Python 2.7.
A feature that I'm trying to implement is the Night Mode, where the user can toggle between a black background and a light one, that switches from light to dark with a click of the toggle button.
from Tkinter import *
from tkSimpleDialog import askstring
from tkFileDialog import asksaveasfilename
from tkFileDialog import askopenfilename
from tkMessageBox import askokcancel
Window = Tk()
Window.title("TekstEDIT")
index = 0
class Editor(ScrolledText):
Button(frm, text='Night-Mode', command=self.onNightMode).pack(side=LEFT)
def onNightMode(self):
if index:
self.text.config(font=('courier', 12, 'normal'), background='black', fg='green')
else:
self.text.config(font=('courier', 12, 'normal'))
index = not index
However, on running the code, it is always in the night mode and the toggle doesn't work. Help.
Source Code: http://ideone.com/IVJuxX
You can import tkinter library (Use capital letter for python 2.7):
import Tkinter
Create tkinter objects...
root = tk.Tk()
...and tkinter button
toggle_btn = tk.Button(text="Toggle", width=12, relief="raised")
toggle_btn.pack(pady=5)
root.mainloop()
Now create a new command button called "toggle" in order to create the effect of "toggle" when you press playing on the relief property (sunken or raised) :
def toggle():
if toggle_btn.config('relief')[-1] == 'sunken':
toggle_btn.config(relief="raised")
else:
toggle_btn.config(relief="sunken")
At the end apply this behaviour on your button:
toggle_btn = tk.Button(text="Toggle", width=12, relief="raised", command=toggle)
The background and fg are set only in the if-clause. You need to set them also in the else clause:
def onNightMode(self):
if index:
self.text.config(font=('courier', 12, 'normal'), background='black', fg='green')
else:
self.text.config(font=('courier', 12, 'normal'))
index = not index
i.e.,
else:
self.text.config(font=('courier', 12, 'normal'), background='green', fg='black')
Here's a code snippet that will help you with the toggle button animation if you would like to. You only need to add the functions that you want to execute when clicking of course, that's up to you.
'''
import tkinter as tk
# --- functions ---
def move(steps=10, distance=0.1):
if steps > 0:
# get current position
relx = float(frame.place_info()['relx'])
# set new position
frame.place_configure(relx=relx+distance)
# repeate it after 10ms
root.after(10, move, steps-1, distance)
def toggle(event):
if button["text"] == "Yes":
move(25, 0.02) # 50*0.02 = 1
button["text"] = "No"
print("Clicked on yes")
elif button["text"] == "No":
move(25, -0.02)
button["text"] = "Yes"
print("Clicked on no")
# --- main --
root = tk.Tk()
frame = tk.Frame(root, background='red')
frame.place(relx=0, rely=0, relwidth=0.5, relheight=1)
# to center label and button
#frame.grid_columnconfigure(0, weight=1)
#frame.grid_rowconfigure(0, weight=1)
#frame.grid_rowconfigure(3, weight=1)
button = tk.Button(frame, text='Yes',width=5,height=1)
button.place(relx=0.25,rely=0.5,relwidth=0.5, relheight=0.1)
button.bind("<Button-1>",toggle)
root.mainloop()
Albe's answer is good but it has some bad coding practices.
Following the same steps:
Import Tkinter as tk
top = tk.TK()
Define your function here and make it work for any button, not hard coded to the specific button you might use.
def toggle(button: tk.Button):
if button.config('relief')[-1] == 'sunken':
button.config(relief="raised")
else:
button.config(relief="sunken")
Then create and pack all the toggle buttons you want.
toggleButton = tk.Button(text="Toggle", width=12, relief="sunken",
command =lambda:toggle(toggleButton))
toggleButton.pack(pady=5)
top.mainloop()
This is better for two reasons. Creating the button object twice is redundant and will lead to buggy code. Hard coding the button to a specific toggle function is unscalable. This solution makes the code reusable and simple to add to. For example, replace that last block with:
for _ in range(4):
b = tk.Button(text="Toggle", width=12, relief="sunken")
b['command']= lambda a=b:toggle(a)
b.pack(pady=5)
And now you get 4 toggling buttons without any additional functions or copy/paste