Cannot type in Python Entry Widget - python

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.

Related

How could I use my defined function as an argument to activate another function?

What I am trying to do is to set the button status to DISABLED whenever the textbox is empty.
I recreated a snippet of my code bellow:
import tkinter
from tkinter import *
root = tkinter.Tk()
textbox = tkinter.Text(root, height=4)
textbox.pack()
button = tkinter.Button(text="Button", height=2, width=20, state=ACTIVE)
button.pack()
def check_if_textbox_is_empty():
if textbox.get("1.0", tkinter.END) == "":
return True
def if_textbox_is_empty():
button["state"] = DISABLED
# Here is the .bind method I tried, but since it doesn't take func as the first arg it doesn't work for me.
# root.bind(check_if_textbox_is_empty, if_textbox_is_empty)
root.mainloop()
How should go about doing this?
What I want to happen is, when my tkinter.Text has no text in it textbox.get("1.0", tkinter.END) == "" I want my button widget to be disabled button["state"] = DISABLED.
What I have tried is the .bind method, but it seems that it only takes tkinter events ex.:"<Keypress>" as an argument.
Based on correct #JRiggles answer
Only when I added .strip() in if textbox.get("1.0", tkinter.END).strip() == "": button started change state.
import tkinter as tk
root = tk.Tk()
textbox = tk.Text(root, height=4)
textbox.pack()
button = tk.Button(text="Button", height=2, width=20, state=tk.ACTIVE)
button.pack()
def on_textbox_change(_event=None):
if textbox.get("1.0", tk.END).strip() == "":
button["state"] = tk.DISABLED
else:
button["state"] = tk.NORMAL
textbox.bind('<KeyRelease>', on_textbox_change)
root.mainloop()
Firstly:
You don't need both
import tkinter
and
from tkinter import *
Common practice is
import tkinter as tk
after which you can instantiate widgets like so (note how the constant for the state is also prefixed with tk. - these constants are, as you may have guessed, part of tkinter)
button = tk.Button(text="Button", height=2, width=20, state=tk.ACTIVE)
However, just stick with import tkinter for now
With that out of the way...
Why not simply enable/disable the button within the if clause of check_if_textbox_is_empty()? You can bind the '<KeyRelease>' event to your text box and have it call the check function.
You have several options, all of which will work the same way in the end.
Option 1
Add an _event parameter to the check_if_textbox_is_empty function.
The leading underscore _ is convention to let people know that the value is unused by the function, and event is the conventional name for the event argument taken by event-driven functions in tkinter. The default value of None isn't strictly necessary, but it's good practice.
def check_if_textbox_is_empty(_event = None):
if textbox.get("1.0", tkinter.END) == "":
button["state"] = tkinter.DISABLED
else:
button["state"] = tkinter.NORMAL
# bind an event handler to your textbox to check it as the user types
textbox.bind('<KeyRelease>', check_if_textbox_is_empty)
Option 2
Use *_args in your function to allow it to accept any number of arguments.
Again, the _ is convention for unused values, and args is convention for this type of parameter
def check_if_textbox_is_empty(*_args): # accept any number of arguments
if textbox.get("1.0", tkinter.END) == "":
button["state"] = tkinter.DISABLED
else:
button["state"] = tkinter.NORMAL
# bind an event handler to your textbox to check it as the user types
textbox.bind('<KeyRelease>', check_if_textbox_is_empty)
Option 3
Use a lambda to absorb the event and call check... as an anonymous function
def check_if_textbox_is_empty(): # no params needed!
if textbox.get("1.0", tkinter.END) == "":
button["state"] = tkinter.DISABLED
else:
button["state"] = tkinter.NORMAL
# bind an event handler to your textbox to check it as the user types
textbox.bind('<KeyRelease>', lambda _event: check_if_textbox_is_empty())
Bonus Info!
The reason for adding the _event parameter (or absorbing it with *_args or using a lambda) is that tkinter events typically generate an event value when triggered, so your code has to be able to accommodate that value.
When you bind a function to an event and that event is triggered, the event object gets passed to your function whether you want it or not!
There are plenty of reasons you might want your function to use the value of event, but for this particular case you don't need it. You can use any of the methods described above to basically throw it out safely.

The tk window won't show even though there is a mainloop

Even though there is a mainloop being called my tk window will not appear. The code used to work but as soon as I coded in the second function in the nums class there is no tk window. I would like for someone to point out the mistake instead of simply handing out the answer.
Can someone please help me fix this problem?
I use Python IDLE 3.8
Image: [1]: https://i.stack.imgur.com/o65WI.png
Code:
from tkinter import *
from random import randint
import time
#number assignments
class nums:
def __init__(self):
self.value=randint(1,100)
def assignnewnums(oldnum1,oldnum2,lbltxt,lbl,answer):
getans = answer.get()
if(getans==str((oldnum1.value+oldnum2.value))):
del(oldnum1)
del(oldnum2)
oldnum1=nums()
oldnum2=nums()
lbltxt="Correct!"
lbl.config(text=lbltxt)
time.sleep(5)
lbltxt="What is {} + {}".format(oldnum2.value,oldnum1.value)
lbl.config(text=lbltxt)
else:
lbltxt="Wrong! Try Again!"
lbl.config(text=lbltxt)
time.sleep(3)
lbltxt="What is {} + {}".format(oldnum2.value,oldnum1.value)
lbl.config(text=lbltxt)
a = nums()
b = nums()
#GUI startup
root = Tk()
#Label
title = Label(root, text="AddPrac", fg="dark blue")
title.pack()
#Question
questxt = "What is {} + {}".format(a.value,b.value)
ques = Label(root,text=questxt,fg="red")
ques.pack()
#UserAnswer
ans = Entry(root)
ans.pack()
#SubmitButton
enter = Button(root,text="Submit Answer!",fg="yellow",command=nums.assignnewnums(a,b,questxt,ques,ans))
enter.pack()
#GUI continued startup
root.mainloop()
I tried your code and the window does appear if you wait a few seconds.
This is due to the following offending code snippet:
command=nums.assignnewnums(a,b,questxt,ques,ans)
This doesn't do what you think it does. You were thinking of:
command=lambda: nums.assignnewnums(a, b, questxt, ques, ans)
The way your code is written now, it does not bind a callback to the button, but rather, calls- and executes the function (since you are invoking it explicitly), and attempts to bind the return value as a callback, which makes no sense. As a side effect of calling the function, the main thread sleeps (since assignnewnums uses time.sleep) for a bit before you reach root.mainloop.
Anytime you are binding a callback to a button, you want to provide a callable object - either just a function object, or if arguments are critical, a lambda or functools.partial.

Why does my tkinter window only work when created globably?

When I created this module I first made the tkinter window (all of its settings globally) it worked as intended. I could run the module and the window worked, taking the input from the entry field and displaying the welcome or error message. But when I put them into a function, it stopped working correctly, as shown.
How the window looks when created globally, with the button and input working:
https://gyazo.com/ffcb16416b8a971c09bfa60ee9367bbd
How it looks when created inside the function:
https://gyazo.com/c8858a2793befafa41e71d1099f021d3
The error message pops up straight away, then the main window with the entry field but not the button.
Here's the code where I created the window and settings inside a function:
def userSign(userEntry):
userId = userEntry.get()
if userId.isdigit() == True and len(userId) == 4:
welcomeWindow = tkinter.Tk()
welcomeWindow.title("Welcome")
welcomeWindow.geometry("200x50")
welcome = tkinter.Label(master=welcomeWindow, text="Welcome "+userId,font=("Helvetica", 18, "bold"))
welcome.grid()
welcomeWindow.mainloop()
else:
errorWindow = tkinter.Tk()
errorWindow.title("ERROR")
errorWindow.geometry("500x50")
error = tkinter.Label(master=errorWindow, text="ERROR: "+userId +" DOES NOT MEET CRITERIA", font=("Helvetica", 18, "bold"))
error.grid()
userId=""
errorWindow.mainloop()
def show():
window = tkinter.Tk()
window.title("Sign In")
window.geometry("250x100")
signInPrompt = tkinter.Label(master = window, text = "Enter your ID to sign in")
signInPrompt.grid(column=0,row=2)
userEntry = tkinter.Entry(master = window)
userEntry.grid(column=0,row=4)
enterButton = tkinter.Button(master = window, text="Sign in", command=userSign(userEntry))
enterButton.grid(column=0,row=6)
window.mainloop()
How do I get it so that my window works correctly when created inside functions as this module needs to be called by a different, main module.
You are creating two instances of Tk() which is a bad idea. Instead use Toplevel() for additional windows.
When you create variables or widgets inside a function the names are in the local scope and not available outside the function. And whan the function ends they will be garbage colletced.
Also, as #fhdrsdg points out, problems in the button command.

How to repeatedly get the contents of a Text widget every loop with tkinter?

I would like to repeatedly get the contents of a Text widget, so I can analyse it and gets stats about what's been entered. These stats would need to be updated in real time as the user types, hence why I need the variable currentContent to update every loop. What I'd like to do is something like this.
main = tk.Tk()
# Y'know, all the typical window setup stuff.
currentContent = inputBox.get(0.0,END)
textBlobContent = TextBlob(currentContent)
# Basically here I'd do a bunch of stuff using TextBlob.
main.mainloop()
However, that doesn't work. It gets the content once, as soon as the window loads, and then stops. Surely mainloop runs repeatedly, and it should keep getting the contents of the Text widget?
A simple solution that works most of the time would be to put a binding on <KeyRelease>. That will enable a function to be called whenever the user is typing. This won't trigger the callback whenever data is pasted with the mouse, or inserted via other means (such as a toolbar button).
A more robust solution is to set up a proxy for the widget, so that an event is generated whenever anything is inserted or deleted in the widget. This proxy can look at what is being done with the widget (insert, delete, selection changed, etc) and generate an event. You can then bind to this event to do whatever you want.
Here's an example of a custom text class that generates a <<TextModified>> event whenever data is inserted or deleted:
import tkinter as tk
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
This proxy does four things:
First, it calls the actual widget command, passing in all of the arguments it received.
Next it generates an event for every insert and every delete
Then it then generates a virtual event
And finally it returns the results of the actual widget command
You can use this widget exactly like any other Text widget, with the added benefit that you can bind to <<TextModified>>.
For example, if you wanted to display the number of characters in the text widget you could do something like this:
import tkinter as tk
# ... import of definition of CustomText goes here ...
root = tk.Tk()
label = tk.Label(root, anchor="w")
text = CustomText(root, width=40, height=4)
label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)
def onModification(event):
chars = len(event.widget.get("1.0", "end-1c"))
label.configure(text="%s chars" % chars)
text.bind("<<TextModified>>", onModification)
root.mainloop()

Simple app creating Python tkinter

I was trying to create a simple app for illustration purposes. The idea is as follows:
Create an application which will run script files associated only to the selected courses (radio buttons). So, I create radio buttons which list out subjects (to click on). Once the subjects are selected the user has to hit the Enter button. This should run all the .py files for the selected subjects (execute_script function).
However, when I run my code, I get 4 messageboxes with 'None' written inside. After clicking ok on them, I get a square windows with only the enter button. What can I do to correct this problem?
def check(file_name, relStatus):
radioValue = relStatus.get()
tkMessageBox.showinfo('You checked', radioValue)
been_clicked.append(file_name)
return
def execute_script():
for name in been_cliked:
subprocess.Popen(['python', 'C:\Users\Max\Subjects\{}'.format(name)])
yield
def main():
#Create application
app = Tk()
app.title('Coursework')
app.geometry('450x300+200+200')
#Header
labelText = StringVar()
labelText.set('Select subjects')
#Dictionary with names
product_names = {}
names = []
file_name = []
names = ['Math', 'Science', 'English', 'French']
file_name = ['calc.py', 'physics.py', 'grammar.py', 'livre.py']
product_names = OrderedDict(zip(names, file_name))
#Create radio buttons
global been_clicked
been_clicked = []
relStatus = StringVar()
relStatus.set(None)
for name,file_name in product_names.iteritems():
radio1 = Radiobutton(app, text=name, value=name, \
variable=relStatus, command=check(file_name, relStatus))
button = Button(app, text='Click Here', width=20, command=execute_script())
button.pack(side='bottom', padx=15, pady=15)
app.mainloop()
if __name__ == '__main__': main()
There are a few issues with your script:
1) A typo in your execute_script() function: for name in been_cliked
2) You are actually calling the check() function when you create your radio buttons. That's why you're seeing the windows pop up when you run your program.
You need to change this:
radio1 = Radiobutton(app, text=name, value=name, \
variable=relStatus, command=check(file_name, relStatus))
to this:
radio1 = Radiobutton(app, text=name, value=name, \
variable=relStatus, command=check)
See how check no longer has brackets? That's means you're passing the function name as an argument, instead of actually calling the function. Of course, you'll see an immediate problem is that you can no longer pass arguments to your callback function! That's a bigger issue. Here's a couple links to help get you started:
How to pass an argument to event handler in tkinter?
How can I pass arguments to Tkinter button's callback command?
Here is the solution:
Change this:
command=check(file_name, reStatus)
to this:
command = lambda: check(file_name, relStatus)
3) You don't actually pack() your radio buttons anywhere. Add something like this just after you create your radio buttons in your for loop: radio1.pack(side='top')
4) You have the same problem with your callback for your Click Here button. You need to change your command to not call the function, but just refer to it: command = execute_script
5) In execute_script(), make sure you import subprocessing
6) Are you sure you want yield instead of return in your execute_script() function?
7) In all your functions, you need to make sure that been_clicked is global.
I think if you fix these issues you'll be closer to getting what you're looking for. Good luck.!

Categories

Resources