Tkinter button command argument - python-3.x

I have been looking into tkinter with python as I am seriously interested in GUi's and thought it would be a great place to start. I went through a good few tutorials like The New Boston set and one or two theres to grab hold of the basics. Now I am trying to pass an 'argument' through a botton so that my program will move on to my IF statement and I am having no joy.
Please find attached code:
try:
from tkinter import *
except ImportError:
from Tkinter import *
eod = 'no'
selection = []
selection1 = 'nothing'
while eod != 'yes':
def beer():
selection.append('Beer')
selection1 = 'Beer'
def wine():
selection.append('Wine')
def whiskey():
selection.append('Whiskey')
welcomeGUI = Tk()
welcomeGUI.geometry('400x200+100+200')
welcomeGUI.title('Drinks Despenser')
welcomLabel1 = Label(welcomeGUI, text='Drinks-O-Matic', font='Times 22 bold').grid(row=0,column=2)
welcomLabel2 = Label(welcomeGUI, text='Please select drink', font='Times 16 bold').grid(row=1,column=2)
beerButton = Button(welcomeGUI, text='Beer', font='Times 16 bold',command=beer()).grid(row=6,column=1)
wineButton = Button(welcomeGUI, text='Wine', font='Times 16 bold').grid(row=6,column=2)
whiskeyButton = Button(welcomeGUI, text='Whiskey', font='Times 16 bold').grid(row=6,column=3)
if selection1 is 'Beer':
welcomeGUI.destroy()
beerGUI = Tk()
beerGUI.geometry('400x200+100+200')
beerGUI.title('Beer Despenser')
beerGUI.mainloop()
welcomeGUI.mainloop()

Ok there is a lot going on here so I have a couple of things that I think will help you.
You need to move your def out of the while loop for all the functions. They should be defined only once in the beginning of the file.
Also, you are assigning variables to the Button object after you call the grid method. That method returns None so you shouldn't do that because you are assigning variables None instead of the actual button object as you intend to. You should assign the variables the button object alone and then call varname.grid() later.
Finally, to address your question: when you write command=beer() you are once again calling the function beer and assigning its return value to the command parameter. When you are using Tkinter you must assign only the function name to the command parameter such as command=beer. However, if you have to pass it arguments you can use lambda. For example: command=lambda: beer(arg1, arg2).
P.S.
When comparing strings you should say
if selection1 == "Beer":
not
if selection1 is "Beer":
is tests for identity not equality and you want to test equality.
EDIT: You also should unindent the try at the top of your file.
Also because selection1 is a local variable in the function beer it won't work, you need to declare it a global
def beer():
global selection1
selection.append('Beer')
selection1 = 'Beer'
Furthermore, you need to destroy the window or the if statement in the while loop won't run.
def beer(window):
global selection1
selection.append('Beer')
selection1 = 'Beer'
window.destroy()
and then you need to pass the welcomeGUI Tk instance to the function like so
beerButton = Button(welcomeGUI, text='Beer', font='Times 16 bold',command=lambda: beer(welcomeGUI)).grid(row=6,column=1)
One last thing. I would remove the while loop all together and have a button on the beer window to call back the main welcome window because putting two mainloops in the while loop is not going to be a good thing.

Related

How do I get data from one function to another

I read about "random" and it got me thinking about helping my kid in reading and writing by making a program where a word is shown and she needs to type it. Because it's a small program I could do it quite easliy with procedural programming, but to make it more 'attractive' to her I decided to fool around with tkinter.
Tkinter forces to create functions which can be called through 'command' and now I have a problem.. If I run the check() function, it doesn't get the variables from the dictee() function. I found several answer from nesting a function in a function (undefined variable problems), or passing arguments with return (ended up in recursion), using global (the list of words wouldn't empty) etc etc.. I couldn't get any of them working... I've been looking for answers, but I can't find the correct solution.. Anyone care to shed their light?
Thanks!
"""import nescessary."""
import sys
import random
def main():
"""Setting up the game"""
print("Aliyahs reading game.'\n")
begin = input("do you want to start? yes or no.\n\n")
if begin == "yes":
dictee()
def dictee():
"""Show random words and ask for input."""
words_correct = 0
words_wrong = 0
vocabulary = ['kip', 'hok', 'bal', 'muis', 'gat'
]
words_passed = []
while True:
if vocabulary == []:
print("\n\nThose were all the words.")
print("Words correct: %d" % words_correct)
print("words wrong: %d" % words_wrong)
one_more_time = input("Do you want to go again? yes or no")
if one_more_time == "no":
print("See you next time.")
input("\nPush enter to close.")
sys.exit()
else:
main()
word = random.choice(vocabulary)
print('\x1b[2J')
print("{}".format(word))
print("\n\n")
words_passed.append("{}".format(word))
vocabulary.remove("{}".format(word))
answer = input("Write the word you saw:\n\n")
check()
def check():
'''Cross check word shown with answer given'''
if answer == word:
print("Nice")
words_correct += 1
else:
print("2bad")
words_wrong += 1
try_again = input("\n\nContinue? yes or no\n ")
if try_again.lower() == "no":
exit_game()
else:
dictee()
def exit_game():
'''summarize results and exit after pushing enter'''
print("Words correct: %d" % words_correct)
print("Words wrong: %d" % words_wrong)
input("\nPress Enter to exit.")
sys.exit()
if __name__ == "__main__":
main()
Ive just made few changes here, and run it and got no errors because I dont know what to type in order to get the right results. Anyways all the changes are related to global and redefinition of values.
# say these outside all the functions, in the main block
words_correct = 0
words_wrong = 0
vocabulary = ['kip', 'hok', 'bal', 'muis', 'gat']
words_passed = []
def dictee():
global word, answer
..... #same bunch of codes
def check():
global answer, words_correct, words_wrong
.... #same bunch of codes
Why do we have to say global? Basically because when we define variables they either get defined on local scope or global scope, variables defined on main block(outside of all function) are on global scope, while inside functions are on local scope. Variables defined on global scope can be accessed anywhere and that on local can only be accessed from within where its defined.
Where do we have to use global? We say global where we define the variable or we redefine, or at least this is what I know of. We need to say global where we declare the variable, like, a = 'good', we also need to say global if we are changing it, a = 'bad' or a += 'day' because we are assigning a new value to it. Using global outside of all functions, on the main block, is useless.
Why are we declaring words_correct and words_wrong outside all functions? It is simply because if you declare and set its value to 0 inside dictee(), each time the function is run the value of those variables will change to 0, which means score will always be 0 or 1, so we define it once only, in the main block. Same logic applies to the two lists(vocabulary and words_passed), each time function runs they reset the list to the full list of words, so to get rid of that, just define it once in the main block.
I also think using parameters here might need re-structuring of your code as your calling both function from each other.
Note that we only say global on functions, out side functions all defined variables are open to global scope and can be accessed from anywhere in the code.
PS: This is just my understanding of global over the span of 4 months, do correct me if im wrong anywhere, thanks :D

Why is it saying a isnt defined even though i have defined you

so i defined a but when i try to type out a using keybaord.type it just says that it isnt defined
i tried making a global that didnt work i tried moving postions of the code and that didnt work ive tried many other things they didnt work either
from tkinter import *
import webbrowser
from pynput.keyboard import Key, Controller
import time
menu = Tk()
menu.geometry('200x300')
def webop(): # new window definition
global a
def hh():
a = "" + txt.get()
while True:
keyboard = Controller()
time.sleep(1)
keyboard.type(a)
keyboard.press(Key.enter)
keyboard.release(Key.enter)
sp = Toplevel(menu)
sp.title("Spammer")
txt = Entry(sp, width=10)
txt.grid(row=1,column=1)
btn = Button(sp, text='spam', command=hh)
btn.grid(row=1,column=2)
def enc():
window = Toplevel(menu)
window.title("nou")
button1 =Button(menu, text ="Spammer", command =webop) #command linked
button2 = Button(menu, text="Fake bot", command = enc)
button1.grid(row=1,column=2)
button2.grid(row=2,column=2)
menu.mainloop()
global a underneath def webop() gives webop access to the variable a in the enclosing scope (the scope up there where you're doing your imports). Since you haven't defined a in that scope, you're getting the error.
Either way, you generally should avoid using globals like that and pass data to functions using arguments. In order to pass arguments into your Button command you can use a closure.
You should move the part of your code accessing a to the part where that value is set
It is unclear what you're trying to achieve here since when you run webop your program will reach while True and continuously loop there and never reach the code below your while loop
For instance
def hh(a):
a = "" + txt.get()
while True:
keyboard = Controller()
time.sleep(1)
keyboard.type(a)
keyboard.press(Key.enter)
keyboard.release(Key.enter)
btn = Button(sp, text='spam', command=hh)
An alternative approach achieves the same thing using functools partial. See https://www.delftstack.com/howto/python-tkinter/how-to-pass-arguments-to-tkinter-button-command/

How to use a variable inside of a function without declaring it as a global variable

I have a 2 part question (if that's not allowed, I really only need to first part answered)
I have the following sample code
import tkinter as tk
window = tk.Tk()
def countIncrease():
count +=1
t1.insert(tk.END,count)
count = 0
t1=tk.Text(window,height=3,width=30)
t1.grid(row=0,column=0,columnspan=3)
b1=tk.Button(window,text="+",height=3,width=10,command=countIncrease)
b1.grid(row=1,column=0)
window.mainloop()
and if I execute this code, I get the error UnboundLocalError: local variable 'count' referenced before assignment
I know that I could simply fix this by adding global count to the function
After I do that, when I press the button, the output is 1, and repeated presses produce 12, 123, 1234, 12345 and so on.
My first (and main) question is that I know it is bad practice to make variables global. What would be the proper way of making this work without making count a global variable?
My second question is how do I make the screen "refresh" so it is only showing the up to date variable, ie instead of 123 its just 3.
You should restructure your code to use a class and make count a class variable if you don't want to use a global variable. And to 'refresh' the screen/tkinter text, you need to delete the content before inserting new one.
Here is one way you can address the two issues:
import tkinter as tk
class app():
def __init__(self, parent):
self.count = 0
self.t1=tk.Text(parent, height=3,width=30)
self.t1.grid(row=0,column=0,columnspan=3)
self.b1=tk.Button(parent,text="+",height=3,width=10,command=self.countIncrease)
self.b1.grid(row=1,column=0)
def countIncrease(self):
self.count +=1
self.t1.delete('1.0', tk.END) #refresh/delete content of t1
self.t1.insert(tk.END,self.count)
window = tk.Tk()
app(window) # Create an instance of app
window.mainloop()

tkinker optionmenu not showing chosen result

import tkinter
window = tkinter.Tk()
def abc(event):
ans=0
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=numberss[ans], command=efg)
ans=ans+1
def efg(event=None):
print('yee')
numbers = ['1','2', '3']
number=['4','5','6']
var = tkinter.StringVar(window)
var1 = tkinter.StringVar(window)
omenu = tkinter.OptionMenu(window, var, *numbers, command = abc)
omenu.grid(row=1)
omenu2 = tkinter.OptionMenu(window, var1, *number, command = efg)
omenu2.grid(row=2)
after you have entered the first option menu, it will update the second one. when you enter data into the second one, it runs the command, but doesn't show you what you entered. i do not want to include a button, and i know that the command works and not on the second
i found some code that changed the options of the second menu, however when i ran this, the command wouldn't work as it was changed to tkinter.setit (i would also like to know what is does. i do not currently understand it)
omenu2['menu'].add_command(label=numberss[ans], command=tkinter._setit(var1, number))
this has been taken from a larger piece of code, and has thrown the same error
You should set your StringVar(var1) new value.
def abc(event):
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=number, command=lambda val=number: efg(val))
def efg(val, event=None):
print('yee')
var1.set(val)
You are using for loop so you don't need ans(at least not in this code) since it iterates over items themselves.

get rid of global variable in python3

First of all I'm using python 3.3 & 3.2 on windows and Linux respectively.
I am starting to build an rpn calculator. It looks like cross-platform key listeners is a kind of holy grail for python. So far this seems to be doing the trick, but I've created other problems:
I cannot get away from the global variable for entries and using my
stack.
It looks like I have to build the program from inside
callback()
Here is a rough skeleton that shows my direction. Am I missing a way to pass information in and out of callback()
The goal was to build an RPN class before i found myself stuck inside callback().
import tkinter as tk
entry = ""
stack = list()
operators = {"+",
"-",
"*",
"/",
"^",
"sin",
"cos",
"tan"}
def operate(_op):
if _op == "+":
print("plus")
def callback(event):
global entry, stack
entry = entry + event.char
if event.keysym == 'Escape': # exit program
root.destroy()
elif event.keysym=='Return': # push string onto stack TODO
print(entry)
entry = ""
elif entry in operators:
operate(entry)
root = tk.Tk()
root.withdraw()
root.bind('<Key>', callback)
root.mainloop()
You have several options to do what you want to do.
1. Use a Class for your application
The canonical way of doing what you wish without resorting to a global variable is to place the application within a class, and pass a method as a callback (see print_contents) the following is straight from the docs:
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.entrythingy = Entry()
self.entrythingy.pack()
# here is the application variable
self.contents = StringVar()
# set it to some value
self.contents.set("this is a variable")
# tell the entry widget to watch this variable
self.entrythingy["textvariable"] = self.contents
# and here we get a callback when the user hits return.
# we will have the program print out the value of the
# application variable when the user hits return
self.entrythingy.bind('<Key-Return>',
self.print_contents)
def print_contents(self, event):
print("hi. contents of entry is now ---->",
self.contents.get())
2. Curry the callback over your state
You can also use Python's functional programming constructs to curry a function over a global variable and then pass the curried function as a callback.
import functools
global_var = {}
def callback(var, event):
pass
#...
root.bind('<Key>', functools.partial(callback, global_var))
Although this probably isn't what you want.
3. Use a global variable
Sometimes, a global variable is ok.
4. Re-architect for cleanliness and readability
However, you most definitely do not have to build your program inside the callback.
In fact, I would recommend that you create a suite of test with various valid and invalid input, and make a Calculator class or function that takes a string input of RPN commands and returns a value. This is easy to test without a tkinter interface, and will be far more robust.
Then, use your callback to build a string which you pass to your Calculator.
If you want incremental calculation (ie, you're building a simulator), then simply make your Calculator accept single tokens rather than entire equations, but the design remains similar. All the state is then encapsulated inside the Calculator rather than globally.

Resources