Python Tkinter: How to combine keyboard prompts and clickable buttons adequately? - python

Hello Stack community,
As I tried writing a simple google searchbar GUI app, I seem to have created a trade-off between either a clickable GUI button or a working keyboard command.
This depends on passing 'self' into the function called 'google'. Without 'self' the GUI Submit button will work, and the Enter key will raise an error in the console. With 'self' passed into google, the Enter key will work, but the GUI Submit button raises the opposite error. It has to do with the amount of arguments passed into this function 'google'.
Is there a way to make both the submit button and the Enter key work?
In this example the GUI Submit button works, the Enter key will give an error:
#!/usr/bin/env python3
from tkinter import ttk
from tkinter import *
import webbrowser
def google():
url = "https://www.google.nl/#q=" + search.get()
webbrowser.open_new_tab(url)
#GUI
root = Tk()
search = StringVar()
ttk.Entry(root, textvariable=search).grid()
submit = ttk.Button(root, text="Search", command=google).grid()
root.bind("<Return>", google)
root.mainloop()

Add a default for the times that nothing is passed to the function. It doesn't matter what it is since you don't use it.
def google(event=None):
print("google function called")
## url = "https://www.google.nl/#q=" + search.get()
## webbrowser.open_new_tab(url)
#GUI
root = Tk()
search = StringVar()
ttk.Entry(root, textvariable=search).grid()
submit = ttk.Button(root, text="Search", command=google).grid()
root.bind("<Return>", google)
root.mainloop()

The reason it fails is that a Tkinter callback function passes an event argument. So any callback you pass has to have this argument. Adding an argument fixes it for the bind but then breaks it for the submit.
This is because for the submit no argument is passed and your function now requires an argument. So basically what it means is you can't use the same function for both purposes.
One simple way to get round this is to use a lambda in the bind call.
root.bind("<Return>", lambda e: google())

Related

simple tkinter question - button command (display other text on click)

i've just started learning tkinter for python, and i'm trying to get the button to change its text when it's clicked on.
this seems like a very simple question, but i can't find any answers. the code i'm using at the moment doesn't work - when the window opens, it displays 'clicked!' as a label above the button immediately, before i've clicked on the button.
from tkinter import *
root = Tk()
def click():
label = Label(root, text = 'clicked!')
label.pack()
button = Button(root, text='click me', command = click())
button.pack()
root.mainloop()
To change an existing button's text (or some other option), you can call its config() method and pass it keyword arguments with new values in them. Note that when constructing the Button only pass it the name of the callback function — i.e. don't call it).
from tkinter import *
root = Tk()
def click():
button.config(text='clicked!')
button = Button(root, text='click me', command=click)
button.pack()
root.mainloop()
You're passing command = click() to the Button constructor. This way, Python executes click, then passes its return value to Button. To pass the function itself, remove the parentheses - command = click.

Tkinter TopLevel Destroy not being detected

So I've given myself a little project and I'm trying to make a little tool to connect to the OKEX exchange. Now I'm currently working on the GUI and I've decided to use Tkinter. After lots of research and what-not, I've come up with the following, but now I've become a bit stuck.
I've got 2 classes, one for the main window and one for the login window. However, some of the functions of the main window rely on what happens after I've submitted the login details. Now I know that Toplevel is for creating additional windows in Tkinter, and you normally close these windows with .destroy() , and if I want to pick up on this event in the main window class then I need to use the Toplevel.protocol("WM_DELETE_WINDOW", function_name) call ...but this isn't working for me.
It works as expected if I close using the cross in the top right, but not if I close with my function that calls .destroy() , can anyone explain to me why this isn't working as intended? Perhaps I have missed something ?
I want to change the text in the first frame to "Logged In" after the user (me) enters in their details, but I need to login first and pass through a user object to contain those details.
Anyways, here's the code, please help me out!
The code in question is the myquit function in the LoginBox class , and the goToLogin function in the MainBox class :)
from tkinter import *
from tkinter.ttk import *
class LoginBox:
def __init__(self, master): # Master is the frame that is passed in.
# Create Frame
self.master = master
self.master.title('~ Please Login ~')
def login_function(self):
user = "xxx"
secret_key = "yyy"
print("User - API Key: " + user)
print("User - Secret Key: " + secret_key)
# Perhaps check the login ...
# if it's a success then quit the function
self.myquit()
def myquit(self):
self.master.destroy()
class MainBox:
def __init__(self, master):
# Set the root window
self.master = master
self.master.geometry("500x500")
self.master.title("OkBot v0.1")
self.master.resizable(False, False)
# Initialize the frames
self.uiFrame1 = Frame(self.master) # The Top Layer -- Login Text + Login Button
# uiFrame1 Initialize --Login Text + Login Button
self.ui1_button = Button(self.uiFrame1, text="Login", command=self.goToLogin).grid(row=0, column=3, sticky=E, padx=1)
# Create Topview for popup , pass in User Object to LoginBox
def goToLogin(self):
loginMaster = Toplevel(self.master)
loginMaster.protocol("WM_DELETE_WINDOW", self.checkLogin) # This is if they close via X
loginGUI = LoginBox(loginMaster)
def checkLogin(self):
print("This function was called -- The Protocol for destroyed windows works")
# Initialize the objects and start the program
mainWindow = Tk()
myProgram = MainBox(mainWindow)
mainWindow.mainloop()
It works as expected if I close using the cross in the top right, but not if I close with my function that calls .destroy() , can anyone explain to me why this isn't working as intended? Perhaps I have missed something ?
loginMaster.protocol("WM_DELETE_WINDOW", self.checkLogin) only tells tkinter what to do when the window manager destroys the window. When you call some function which calls destroy(), that doesn't involve the window manager and therefore your callback is not called.
If you want something to happen when the window is destroyed, you should bind to the <Destroy> event.

Tkinter Open New Frame Without A Button Press

I have a multi-frame Tkinter program running and am in a situation where I need a new frame to open without a button press.
If I were to have a button it would be coded like this:
button = tk.Button(self, text="New Window",
command=lambda: controller.show_frame("NewWindow"))
Is there a way I can make a new window open after a time.sleep(60) command? This is what I have tried:
def on_button(self):
if LogIn in Data:
time.sleep(5)
print("Welcome")
root.after(6, controller.show_frame("HomePage"))
else:
print("please register")
Various Data and coding goes after the defining. If the condition is true it 'Logs In' This is when I want it to show the new frame
Root gives the error of: NameError: name 'root' is not defined
Controller.after(etc) gives : NameError: name 'controller' is not defined
Despite controller being used frequently without issue throughout the rest of the program
I would recommend the after callback See: Alarm handlers and other non-event callbacks
after(delay_ms, callback=None, *args) [#]
Registers an alarm callback that is called after a given time.
root.after(60000, new_window_func, args)

Cannot type in Python Entry Widget

I've got an interesting problem with the tk Entry widget. If I run the following test code,
from Tkinter import *
root =Tk()
def pfunc(self):
print Input.get()
f=Frame(root)
f.pack()
Input=Entry(f)
#Input.bind("<Return>",pfunc)
Input.pack()
root.mainloop()
I can properly enter into the widget and print to console; however the following code, as part of a larger GUI, does not allow me to click in the Entry boxes at all.
self.Tlabel = Label(self.TempFrame, text="Temp")
self.Tlabel.pack( side = LEFT)
self.Tenter = Entry(self.TempFrame,width=10, bd =5)
self.Tenter.bind("<Return>",self.getFlux)
self.Tenter.pack (side=RIGHT)
self.Flabel = Label(self.FluxFrame, text="Flux")
self.Flabel.pack( side = LEFT)
self.Fenter = Entry(self.FluxFrame, width=10, bd =5)
self.Fenter.bind("<Return>",self.getTemp)
self.Fenter.pack(side = RIGHT)
def getFlux(self):
for i in range(len(self.fit_tuples)):
if self.fit_tuples[i][0]==self.currentBFMdate and self.fit_tuples[i][1]==self.cell.get():
fit_data=self.fit_tuples[i][2]
self.Fenter.set(fit_data[0]*np.exp(fit_data[1]*int(self.Tenter.get())))
else:
self.Fenter.set("Invalid")
def getTemp(self):
for i in range(len(self.fit_tuples)):
if self.fit_tuples[i][0]==self.currentBFMdate and self.fit_tuples[i][1]==self.cell.get():
fit_data=self.fit_tuples[i][2]
self.Tenter.set(np.log(float(self.Fenter.get())/fit_data[0])/fit_data[1])
else:
self.Tenter.set("Invalid")
Furthermore, if I run both codes on a separate windows PC I have the same problem. The only difference I can possibly think of is that I am using instance variables within a class; but it seems that other widgets are bound and working properly.
Basically, the bind method is passing "Return" as a parameter to getTemp. As another user suggested, just add another parameter to the function.
If you use bind method, callback is called with an event object. You should add event parameter to the method/function. (See Events and Bindings - An Introduction to Tkinter )
So, rpelcae following lines:
def getFlux(self, event):
...
def getTemp(self, event):
...
The first program work unintentionally. Its parameter name should be event, not self.

Python 2.7 Tkinter open webbrowser on click

from Tkinter import *
import webbrowser
root = Tk()
frame = Frame(root)
frame.pack()
url = 'http://www.sampleurl.com'
def OpenUrl(url):
webbrowser.open_new(url)
button = Button(frame, text="CLICK", command=OpenUrl(url))
button.pack()
root.mainloop()
My goal is to open a URL when I click the button in the GUI widget. However, I am not sure how to do this.
Python opens two new windows when I run the script without clicking
anything. Additionally, nothing happens when I click the button.
You should use
button = Button(root, text="CLCK", command=lambda aurl=url:OpenUrl(aurl))
this is the correct way of sending a callback when arguments are required.
From here:
A common beginner’s mistake is to call the callback function when
constructing the widget. That is, instead of giving just the
function’s name (e.g. “callback”), the programmer adds parentheses and
argument values to the function:
If you do this, Python will call the callback function before creating
the widget, and pass the function’s return value to Tkinter. Tkinter
then attempts to convert the return value to a string, and tells Tk to
call a function with that name when the button is activated. This is
probably not what you wanted.
For simple cases like this, you can use a lambda expression as a link
between Tkinter and the callback function:
Alternatively, you don't have to pass the URL as an argument of the command. Obviously your OpenUrl method would be stuck opening that one URL in this case, but it would work.
from Tkinter import *
import webbrowser
url = 'http://www.sampleurl.com'
root = Tk()
frame = Frame(root)
frame.pack()
def OpenUrl():
webbrowser.open_new(url)
button = Button(frame, text="CLICK", command=OpenUrl)
button.pack()
root.mainloop()

Categories

Resources