This is my code:
from Tkinter import*
from random import radint
Characters=[Percy,Annabeth,Leo,Chuck,Sarah]
Used = []
def callback():
selected = None
end = len(Characters)-1
rando = randint(0,end)
selected = Characters[rando]
for i in Characters:
if i in Used:
print 'This has already been used'
else:
Characters[rando]()
Used.append(Characters[rando])
game = Tk()
game.geometry('50x50+700+100')
Button1 = Button(game,text = '1',command =lambda:callback() )
Button1.pack(side=LEFT)
game.mainloop()
What is supposed to happen is, the program runs, 1 button is in the window, you click the button,it randomly selects one of the values from the list 'Characters' (all of which are procedures), executes the procedure and then adds selected value to the list 'Used', then when you click the button again, it checks if that value has already been used, and if it has, it prints 'This has already been used' if it has not, it executes.
But what is happening, is that it will run the same procedure multiple times, and then when I close the root window (game) it re-executes all of the procedures that were executed when I hit my button.I have been working at this for an hour and cannot figure it out.
for i in Characters: goes through all of the items in the list Characters each time it is run.
Suppose you start up your app, and click the button. This is what happens:
for i in Characters:
i = Percy
if i in Used:
... #i is not in Used
else:
... #execute Percy() and add it to Used
i = Annabeth
if i in Used:
... #i is not in Used
else:
... #execute Annabeth() and add it to Used
...you get the picture. So the 1st time it is run, all of the characters are executed, and added to Used.
You should probably change that code to something like this:
def callback():
# code ...
rando = randint(0, end)
if Characters[rando] in Used:
print "This has already been used"
else:
Characters[rando]()
Used.append(Characters[rando])
Your problem is that the very first time you click the button it will add all of the characters to the list. It may appear it happens when your program ends, but it's actually happening all at once.
Assuming you want to process a single instance of Character on each click, you need to add a break statement to terminate your loop after appending something to the list.
for i in Characters:
if i in Used:
print 'This has already been used'
else:
Characters[rando]()
Used.append(Characters[rando])
break
Related
I tried asking this question earlier, but I think this is a better way to ask.
To start:
I am using Windows, and Python 3, with Tkinter.
I am trying to, on button press, update the first row of my grid, namely page_label to add a single character over time, and I'm currently attempting this using time.sleep() inside a for loop.
This works extremely well when using sys.stdout.write() without a gui, but I couldn't manage to get anything to write to a label directly, rather than my terminal.
So, I figured I should be able to use the .config from tkinter, and just update the label for each character, with a small time delay between each update.
However, when I run the following code, the time delay stacks and the update to the config doesn't run until the end.
So, when I first run the app, I get my initialized text in page_label.
Then after what I assume is the completion of the for loop, the final new statement displays as the label updates.
I would be happy with either my current workaround to work, or if there is a nice, clean way to get stdout to write to the label directly, I'd be extremely pleased.
import time
from tkinter import *
from tkinter.ttk import *
from PIL import Image, ImageFont, ImageTk, ImageDraw
import json
root = Tk()
root.title('actiread test')
root.iconbitmap("C:\\Users\\nicho\\Desktop\\Code Puzzles\\actiread\\actireadico.ico")
root.geometry("800x800")
#button triggering time delay text
def get_input():
words = ""
statement = "I need this to print in time delay"
for char in statement:
words = words + char
page_label.config(text = words)
time.sleep(0.2)
#creating updating label
page_label = Label(root, text = "I want to start with an introduction")
#creating button
entry_button = Button(root, text = "Start", command = get_input)
page_label.grid(row = 0, column = 0, columnspan = 2)
entry_button.grid(row = 1, column = 0, pady = 10, sticky = EW)
root.mainloop()
Please ignore the useless imports, I have more to add to the program that I believe will require them.
If it helps, What I'm trying to accomplish will eventually require user inputs that will be saved for later use, and I'll be moving through several versions of this to move through a storyline.
TIA!
The problem is that, when you use time.sleep(), it blocks the tkinter event loop from processing events and your GUI is stuck since it cannot process events. The right way to do this, is by using root.after(ms, func, *args) method which will repeat a function after a given amount of time.
def get_input(words="",count=0):
statement = "I need this to print in time delay"
if count < len(statement):
words += statement[count] # Index and get the current letter and add to the current string
page_label.config(text=words)
count += 1
root.after(200,get_input,words,count) # Call the function passing arguments words and count
And another hacky way around this is to use root.update inside the for loop to force tkinter to process the events, but this is not a good practice.
And a relatively easier solution might be to use global variables instead to understand better:
count = 0
words = ""
def get_input():
global count, words
statement = "I need this to print in time delay"
if count < len(statement):
words += statement[count]
page_label.config(text = words)
count += 1
root.after(200,get_input)
Do note that 200 passed on, is the time for delay in milliseconds, which is same as 0.2 seconds that was passed onto time.sleep
I'm trying to create a button in Tkinter that only appears (becoming visible) when it finds the word "hello" in a textbox.
I could imagine using Threads, and Global variables but i do not know how to code it,
I've imagined something like :
import Tkinter as *
from threading import Thread
window = Tk()
active = True
def check_for_word():
global active
textbox1 = textbox.get("1,0", "end")
while active == True:
if "hello" in textbox1:
button.pack()
else:
button.pack_forget()
save_button = Button(window)
textbox = scrolledtext.ScrolledText(window)
textbox.pack()
threading = Thread (target=check_for_word)
threading.start()
window.mainloop()
this is something I would suspect to work but ends up not, the button either doesn't show at all like the code isn't even running, or the thread doesn't work properly. So am I doing something wrong, if so, can you help me, please? Thank you!
You don't need to make use of threads to do this, you can use tkinter event bindings instead.
def check_for_word():
if "hello" in textbox.get("1.0", "end"):
save_button.pack()
else:
save_button.pack_forget()
save_button = Button(window)
textbox = scrolledtext.ScrolledText(window)
textbox.bind("<KeyRelease>", lambda event:check_for_word())
textbox.pack()
To make a binding, you use widget.bind. In this case, the widget is textbox and it's binding to <KeyRelease>, which is when the user releases a key. It then calls check_for_word when a key is released. (The lambda part is to ignore the event parameter). check_for_word then does what it did before.
You have to put the textbox1 assignment inside the while loop and before the if condition, otherwise it will check the value one time before entering the loop and will keep checking always the same value.
I also want to point out that the in operator is case sensitive and return True if it find even just a substring inside the variable you are checking and not just the precise single word (but I'm not shure if this is intensional).
For the while loop you don't necessarily need a global variable, you could just use while True: if you want it to continuously check the condition (if you want the button to disappear after the user cancel the word).
first of all let me start off by saying I started coding around 6 weeks ago in self-study, so it is very likely, that the approach may still be a total mess.
I have a program running (fine for me) in cli, but I want to make it usable for people that faint, when they see plain white text on a black background which they have to operate without a mouse.
Besides my main window, which is running in the background I wanted to have a message window, which displays the information if all necessary files where selected. which is shown below.
files_to_open = {'File_1':'', 'File_2':'', 'File_3':''}
def selectfiles_window():
global message_window
message_window = Tk()
...
content = Label(message_window, text=get_open_file_status_txt(), **txt_status_general)
content.pack(side='top')
button_select_file1 = Button(message_window,text = 'File 1',font = txt_general['font'],command = lambda:(
select_file_action(name='File 1', filetypename='Excel workbook', extension='*.xlsx'),
content.configure(text=get_open_file_status_txt())))
button_select_file1(side='bottom')
message_window.mainloop()
def select_file_action(name, filetypename, extension):
global files_to_open
files_to_open[name] = filedialog.askopenfilename(title = f'Select {name}', filetypes=[(filetypename, extension)])
def get_open_file_status_txt():
global files_to_open
message =f'''
[File_1] is {"NOT SET" if (files_to_open["File_1"] == "") else "SET"}'''
return message
I expected, that the text is updated after the filedialog was closed (which is partly working as expected).
Now what I don't understand: If I click the button to select File_1 and cancel it for the first time, the value for key File_1 is set to (). Any time after that, if I click the button to select File_1 and cancel it, the value for key File_1 is set to ''. If I select a file the path is saved correctly as value (also on the first attempt). If I cancel it is set again to ''.
Anybody an idea about why the value is set to () on the first cancel but afterwards runs as expected?
I would also be grateful for a different solution to update the text, if my approach is totally off.
Thank you and best regards,
Thomas
Turns out, that the function call was not the issue, rather that it is a (strange to me but maybe intended) behavior of filedialog.askopenfilename, which returns an empty tuple if cancel is selected on first call but an empty string on every further canceled calls.
I'd like to create an Entry with Tkinter where the user can type its telephone number and the text dynamically changes in order that once finished it becomes like +34 1234567890.
In my code the function .icursor(n), used to set the cursor position, at first does not work properly, but then, surpassed the prefix, it does.
This is my code snippet (It belongs to a much larger one).
from Tkinter import *
def TelephoneCheck(self,Vari):
Plain = Vari.get()
Plain = list(Plain)
Plain_flat = []
for element in Plain:
try:
check = int(element)
Plain_flat.append(element)
except: pass
if len(Plain_flat) > 2:
Plain_flat.insert(2,' ')
Plain = ''.join(Plain_flat)
Plain = '+'+Plain
self.istn.set(Plain)
self.InsertTelephoneNumber.icursor(len(Plain))
def CreateInsertTelephoneNumber(self,X,Y,color='white'):
self.istn = StringVar()
self.istn.trace('w', lambda name, index, mode, istn=self.istn: self.TelephoneCheck(istn))
self.InsertTelephoneNumber = Entry(Body,textvariable=self.istn)
self.InsertTelephoneNumber.config(bg=color)
self.InsertTelephoneNumber.place(height=20,width=230,y=Y+27,x=X+245)
def LabelBody(self,X,Y):
TelephoneText = Label(Body,text='Telephone Number *')
TelephoneText.place(y=Y+4,x=X+243)
self.CreateInsertTelephoneNumber(X,Y)
As you see, theoretically, the position should be setted at the end of the string everytime the user adds a number.
I can not understand why it works like a charm only after the prefix and not when the first number is typed (It results as +(Cursor here)3 instead of +3(Cursor here)).
If more code is needed I will update the post.
Thanks for your time and help!
The problem is that you're setting the cursor, but then the underlying widget sets the cursor the way it normally does. Because you're inserting characters into the widget in the middle of Tkinter processing a key press and release, it gets confused. For example, on the very first keystroke it thinks the cursor should be at position 1, but you've inserted a character after that position so the cursor ends up between characters.
The simplest solution is to schedule your change to happen after the default behavior by using after_idle:
Body.after_idle(self.InsertTelephoneNumber.icursor, len(Plain))
Okay, so I'm just trying to get some clarification on why my code is not working like I thought it would.
I am building a GUI, and I want to display text on a Label with a text variable. I have already made a function that updates the Label when the function is called, but of course that is not my problem.
My problem stems from me trying to implement a "print one letter at a time" type of label. While it prints to the terminal in the way I want it to, the label widget only updates after the whole function has finished (visually its the same as just printing the whole string instead of printing a letter at a time).
So what am I missing, what do I not understand? Can you guys help me? Let me post some code so you guys can see where my error is.
I tried both of these independently and they both game me the same result, which was not what I desired.
def feeder(phrase):
"""Takes a string and displays the content like video game dialog."""
message = ""
for letter in phrase:
time.sleep(.15)
message += letter
information.set(message)
#print message
def feeder2(phrase):
"""Same as feeder, but trying out recursion"""
current.index += 1
if current.index <= len(phrase):
information.set(phrase[:current.index])
time.sleep(.1)
feeder2(current.status())
I'm not positive if I need to post more code, so you guys can understand better, but if thats the case, I will do that.
Those 2 functions are used in this function
def get_info():
"""This function sets the textvariable information."""
#information.set(current)
feeder2(current.status())
Which in turn is used in this function
def validate():
""" This function checks our guess and keeps track of our statistics for us. This is the function run when we press the enter button. """
current.turn += 1
if entry.get() == current.name:
if entry.get() == "clearing":
print "Not quite, but lets try again."
current.guesses -= 1
if entry.get() != "clearing":
print "Great Guess!"
current.points += 1
else:
print "Not quite, but lets try again."
current.guesses -= 1
print current
get_info()
entry.delete(0, END)
current.name = "clearing"
The UI will update every time the event loop is entered. This is because painting is done via events (also known as "idle tasks" because they are done when the UI is otherwise idle).
Your problem is this: when you write a loop and do time.sleep, the event loop will not be entered while that loop is running, so no redrawing will occur.
You can solve your problem in at least a couple different ways. For one, you can just call update_idletasks which will refresh the screen. That will solve the repainting, but because you are sleeping the UI will be unresponsive during your loop (since button and key presses aren't "idle tasks").
Another solution is to write a function that takes a string, pulls one character off the string and adds it to the widget. Then it arranges for itself to be called again via the event loop. For example:
import Tkinter as tk
class App(tk.Tk):
def __init__(self,*args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.label = tk.Label(self, text="", width=20, anchor="w")
self.label.pack(side="top",fill="both",expand=True)
self.print_label_slowly("Hello, world!")
def print_label_slowly(self, message):
'''Print a label one character at a time using the event loop'''
t = self.label.cget("text")
t += message[0]
self.label.config(text=t)
if len(message) > 1:
self.after(500, self.print_label_slowly, message[1:])
app = App()
app.mainloop()
This type of solution guarantees that your UI stays responsive while still running your code in a loop. Only, instead of using an explicit loop you add work to the already running event loop.