Text entry event? - python

As part of my program I ask the user for their name and class (high school class). Once the user presses 'Enter' after typing their name the button is disabled and the 'tutor' field appears. However, the user is in essence able to submit their name even if they haven't typed anything. I only want the 'Enter' button to be active once the user has started typing.
What I have done below doesn't seem to work :(
Also, my input validation doesn't work - know why?
class Enter_Name_Window(tk.Toplevel):
'''A simple instruction window'''
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.text = tk.Label(self, width=40, height=2, text= "Please enter your name and class." )
self.text.pack(side="top", fill="both", expand=True)
name_var = StringVar()
def validate_enter_0():
self.Enter_0.config(state=(NORMAL if name_var.get() else DISABLED))
print("validate enter worked")
name_var.trace('w', lambda name, index, mode: validate_enter_0)
enter_name = Entry(self, textvariable=name_var)
enter_name.pack()
enter_name.focus_set()
def callback():
if len(name_var) > 10 or any(l not in string.ascii_letters for l in name_var):
print("Input validation worked")
self.display_name = tk.Label(self, width=40, height=2, text = "Now please enter your tutor group.")
self.display_name.pack(side="top", fill="both", expand=True)
tutor_var = StringVar()
def validate_enter_2():
self.Enter_0_2.config(state=(NORMAL if tutor_var.get() else DISABLED))
print("validate enter worked")
tutor_var.trace('w', lambda name, index, mode: validate_enter_0_2)
tutor = Entry(self, textvariable=tutor_var)
tutor.pack()
tutor.focus_set()
self.Enter_0.config(state="disabled")
self.Enter_0_2 = Button(self, text="Enter", width=10, command=self.destroy)
self.Enter_0_2.pack()
self.Enter_0 = Button(self, text="Enter", width=10, command=callback)
self.Enter_0.pack()

The first obvious problem is this line:
tutor_var.trace('w', lambda name, index, mode: validate_enter_0_2)
You've created a function that takes three variables, and returns the validate_enter_0_2 function as a function object. That doesn't do any good.
You want to create a function that calls the validate_enter_0_2 function. Like this:
tutor_var.trace('w', lambda name, index, mode: validate_enter_0_2())
You have the exact same problem with name_var and will also need to fix it there, of course.
On top of that, you don't actually have a function named validate_enter_0_2 to call, because you defined it as validate_enter_2. This means your validation function just raises a NameError instead of doing useful. Or, if you have a validate_enter_2 function defined somewhere else in your code, it calls the wrong function. (This is one reason that cryptic names like enter_0_2 and enter_2 are not a good thing.)
There's at least one other problem with your code: You're repeatedly trying to use name_var, which is a StringVar object, as if it were a string. You can't do that. And if you actually look at the console output, Tkinter will tell you this, with tracebacks like this:
Exception in Tkinter callback
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1410, in __call__
return self.func(*args)
File "tkval2.py", line 25, in callback
if len(name_var) > 10 or any(l not in string.ascii_letters for l in name_var):
AttributeError: StringVar instance has no attribute '__len__'
And that exception is happening before you get a chance to create the new Entry.
To fix that, you need to call get on the StringVar whenever you want to get its value, like this:
if len(name_var.get()) > 10 or any(l not in string.ascii_letters for l in name_var.get())
Finally, as I explained in the answer to your other question, your trace validator isn't going to get called until something changes. This means you will either need to call it explicitly, or explicitly name_var.set(''), or just start the button off disabled. As written, it will start off enabled, and only disable if you type something and then erase it.
I'm not sure whether those are the only problems with your code, but all of them will certainly prevent your validation from working as expected. So, you need to fix all of them, and any other errors in your code, before it will work.
From your comments:
I am however wondering how to create a pop up message displaying an error…
When do you want to do that? What condition do you want to check, and when do you want to check it?
At any rate, as in most GUIs, the way to "pop up" anything like this is a dialog. Dialog Windows in the Tkinter book explains everything you need to know. But you don't need to copy-paste all that code, or write it from scratch; the stdlib comes with Tkinter helper modules that do most of the work for you. In your case, you probably just want tkMessageBox.
… and something that forces the user to re enter their name
Force them how? Just erasing the existing Entry contents would leave them with an empty box to fill in, which would also disable the button. Is that what you want?
Anyway, guessing at what you want, it could look something like this:
def callback():
if len(name_var.get()) > 10:
tkMessageBox.showerror("Bad name", "Your name is too long. What's wrong with good American names like Joe?")
name_var.set('')
return
# the rest of your code here
In the callback function (called when they click the button after typing their name), instead of just checking some condition and printing something out, I check a condition and pop up an error dialog, clear out the existing name, and return early instead of creating the second half of the form. I didn't handle your other condition (any non-ASCII letters), but it should be obvious how to add that.
However, validation like this might be better done through actual validation—instead of making them wait until they click the button, catch it as soon as they try to type the 11th character, or a space or accented character, or whatever else you don't like. Then you can pop up a message box, and either disable the button until they fix it, reject/undo the change (which is easier with a validatecommand function than with a trace function, as shown in my answer to your previous question).
One last thing: Instead of a message box, it may be better to just embed the error as, say, a Label that appears in the form itself (maybe with the error description in red, with a big flag icon). This is common in web apps and in more modern GUIs because it provides more immediate feedback, and is less obtrusive to the user flow.

Related

Python 3, Keyboard module -How to stop the default action when adding a hotkey (workaround found)

I'm trying to overwrite an existing keyboard function on the enter key with a custom hotkey. The problem is, I cannot stop the default action from occurring also. Worse yet, it occurs after the custom action, so I don't have the chance to retroactively correct it as well.
Here are the relevant parts of the code:
from tkinter import *
from tkinter import ttk
from keyboard import *
root = Tk()
root.geometry('400x300')
'''This is the problematic function called by the hotkey'''
def entr_key(text):
'''Enter key results in printing of the line to the right of cursor'''
curs_pos = text.index(INSERT)
right_hand = text.get(curs_pos,END)
right_hand = right_hand.split('\n')[:1] #lines -> strs in a list, selects the first
print (right_hand)
return
'''THIS IS THE MAIN WIDGET FOR THIS FRAME'''
text_view = ttk.Frame(root, padding=10)
text_view.grid()
text_box = Text(text_view, width=45, wrap=WORD)
text_box.grid(column=0, row=1)
text_box.insert('1.0', 'This is a Text widget demo,\nThis text is aimed at testing the enter key function')
add_hotkey("enter", lambda: entr_key(text_box))
root.mainloop()
(I've changed up some variable names to be more understandable, apologies if I missed any but it's not the source of the problem!)
I've also (unsuccesfully) tried other ways of doing this, eg:
while True:
if is_pressed('enter'):
entr_key(text_box)
'''AND ALSO'''
on_press_key("enter", lambda x=None: entr_key(text_box))
Just to be clear, I don't want the default action of enter key moving the text to a new line.
I need either a way to "break" the key event so that only the custom action takes place, or a way for the custom action to occur after default action, so I can retroactively edit it out
EDIT!!!
I've found a workaround: at the start of entr_key() I call time.sleep(0.01). During this, the default action of the enter key occurs first, and I can retroactively edit it out when the custom function resumes. The delay is slight enough to not be noticeable at all.
But still, if anyone knows how to prevent the default action from occurring completely, I would really appreciate it.

Calling tk.StringVar.set() on a tk.Entry textvariable causes validate="focusout" to stop getting called

The question's in the title, essentially: how do I get the validatecommand callback to continue being called after setting the Entry's textvariable? Here's the Minimum Working Example (MWE):
import tkinter as tk
root = tk.Tk()
sv = tk.StringVar()
def callback():
print(sv.get())
sv.set('Set Text.')
return True
e = tk.Entry(root, textvariable=sv, validate="focusout",
validatecommand=callback)
e.grid()
e = tk.Entry(root)
e.grid()
root.mainloop()
Note that the second tk.Entry widget is there to allow the first one to lose focus, which is the event we're trying to capture.
As the code is now, when you run it, you can change the top Entry widget's text once. It'll correctly get set to Set Text. Then, if you try to change the Entry's text again, the new text will be in the widget, but the callback doesn't happen.
On the other hand, if you comment out the sv.set('Set Text.') code, this behavior completely disappears, and the callback gets called as many times as you wish.
How can I have the sv.set() functionality, while still maintaining the callback getting called every time the Entry widget loses focus?
This is discussed in the Tk manual page for entry:
The validate option will also set itself to none when you edit the entry widget from within either the validateCommand or the invalidCommand. Such editions will override the one that was being validated.
Presumably, this is done to avoid infinite recursion.
You can run this (instead of the given Tcl code, after idle {%W config -validate %v})
root.after_idle(lambda: e.config(validate="focusout"))
from the callback to schedule a reconfiguration of the widget to enable validation again (after changing your sources so that e is the right Entry widget, i.e. not the second one).

Validation on Tkinter.Text widget?

What are my options for getting validation with the Tkinter.Text widget? I don't require Text's advanced functionality, just its multiline attribute. Unfortunately, it lacks both textvariable and validate commands, so it seems that I cannot attach some kind of callback that checks things every time the text changes. I'd like to avoid having to bind to <KeyRelease>, as that looks to capture ALL keypresses, including the likes of Shift, Ctrl, etc, keys, and would appear to be a bit of a mess to work right.
I basically just need to check if the Text field is blank or not, and enable/disable an "Ok" button as appropriate (i.e., if no text, then the button is disabled).
In lieu of this, has anyone come across a decent subclass of Entry that adds multiline functionality that is written in Python? There is this, which adds textvariable to Text, but it is written in pure TCL, not Python, and seems like it would be difficult to integrate into my existing Python environment.
The binding to the <KeyRelease> button does not need to be messy, you don't have to check the value of the key pressed but fetch the content of the widget. Keep in mind that it always has a '\n' at the end, so when you retrive the contents don't forget to discard it:
content = text.get(1.0, "end-1c")
Then you just need to change the state of the button based on this value:
import Tkinter as tk
def configure_ok_button(event):
content = event.widget.get(1.0, "end-1c")
state = "active" if content else "disabled"
button.configure(state=state)
root = tk.Tk()
text = tk.Text(root)
button = tk.Button(root, text="Ok", state="disabled")
text.bind("<KeyRelease>", configure_ok_button)
text.pack()
button.pack()
root.mainloop()

Python Tkinter Calculator wont evaluate text from entry widget

I am trying to make a simple calculator app using tkinter, but everytime I run the code below i get an error message saying
Traceback (most recent call last):
File "C:\Python33\Lib\site-packages\pythonwin\pywin\framework\scriptutils.py", line 326, in RunScript
exec(codeObject, __main__.__dict__)
File "C:\Users\csp\Python\Calculator App.py", line 17, in <module>
solved = eval(expression)
File "<string>", line 0
^
SyntaxError: unexpected EOF while parsing
CODE:
from tkinter import *
tk = Tk()
tk.title('Calculator')
inp = Entry(tk,text="Enter Expression Here",width=20)
inp.pack()
exit = False
def exitbtn():
global exit
exit = True
return exit
btn = Button(tk,text="Quit?",command=exitbtn)
btn.pack
canvas = Canvas(tk,width=200,height=200)
canvas.pack()
while not exit:
expression = inp.get()
solved = eval(expression)
canvas.create_text(100,100,text=expression,font=('Times', 15))
canvas.create_text(100,150,text=solved,font=('Times', 15))
if exit == True:
break
tk.destroy()
i am really new to Python and dont understand why the "solved = eval(expression)" line wont work. please help
So, the reason why eval is not working is because when you first start your program, expression is just an empty string. If you go to the python shell, and type in eval(''), you'll see the same error appear.
One solution would be to check if expression is an empty string or not, and do something like this:
expression = inp.get()
if expression != '':
solved = eval(expression)
else:
solved = '?'
However, even after you apply this fix, your program won't work, for unrelated reasons. The primary reason is that you never call tk.mainloop() (or whatever it's called), so the window will not show up.
This is because of your while loop -- what you wanted to do was to constantly check the input field and update your canvas whenever you get new input after running it through eval.
However, GUI programs, in general, don't work that way and require a different mindset and approach while writing them. Instead of writing loops to check and update program state, you write functions that will automatically be called whenever the program state changes (which are called events). It'll feel a bit backwards at first, but over time it'll help make your code cleaner and easier to manage.
You're actually already doing this in one part of your program -- with your exitbtn function. Now, you just need to convert your while loop into a similar function and bind it to the Entry object.
EDIT:
Here's some example code that does what you want:
import sys
from tkinter import *
# Create the GUI
tk = Tk()
tk.title('Calculator')
inp = Entry(tk, text="Enter Expression Here", width=20)
inp.pack()
btn = Button(tk, text="Quit?")
btn.pack()
canvas = Canvas(tk, width=200, height=200)
canvas.pack()
# Create callback functions
def end_program(event):
'''Destroys the window and ends the program without needing
to use global variables or a while loop'''
tk.destroy()
sys.exit() # Automatically ends any Python program
def update_canvas(event):
'''Gets the input, tries to eval it, and displays it to the canvas'''
expression = inp.get()
try:
solved = eval(expression)
except SyntaxError:
# The expression wasn't valid, (for example, try typing in "2 +")
# so I defaulted to something else.
solved = '??'
canvas.delete('all') # remove old text to avoid overlapping
canvas.create_text(100, 100, text=expression,font=('Times', 15))
canvas.create_text(100, 150, text=solved,font=('Times', 15))
# Bind callbacks to GUI elements
btn.bind('<Button-1>', end_program)
inp.bind('<KeyRelease>', update_canvas)
# Run the program
tk.mainloop()
Some things to note:
I moved your code for checking inp and writing to the canvas to the update_canvas function, and got rid of the while loop.
The update_canvas function will automatically be called whenever somebody lets go of a key while typing in the inp object (the <KeyRelease> event).
This can cause some problems -- this will mean your update_canvas function will be called while the user is in the process of typing text into your calculator. For example, what if the user types in 2 + 2 *? It's not a complete expression, so can't be parsed by eval.
To solve this, I just wrapped eval in a try-except to prevent any bad input from mucking up the program.
Similarly, end_program will be called whenever somebody left-clicks on the btn object (the <Button-1> event).

Making Entry widget "disabled" like in windows calc

I've got a problem with Entry widget while making a copy of Windows Calc.
I have made buttons like in windows calc and I also bind the keyboard 1234567890 + - / * % buttons, to make the same things as the calc buttons.
The mainly problem was that I wanted the Entry to store only numbers and let user input only numbers... but after searching many topics about validatecommand and also looking at windows calc I decided that validatecommand isn't the thing I need - I don't know how to make it validate every character the user inputs to the entry box and after making the keyboard binds, when I am in entrybox and press "1" to write the number it does it two times, because the keyboard event binding inserts the "1" to the entry box too.
So, the thing I want to make is to make entry widget work like the Windows Calc.exe entry box.
The windows calc entry box doesn't let you insert any other character then numbers and also doesn't let you to put your cursor into the entry box...,
it looks like this:
-entrybox is disabled BUT it looks like ENABLED
-numbers and operations can be made by calc buttons or by keyboard buttons
I tried getting this effect by disabling the entry widget at start, and making all buttons functions like that:
-enable the entry widget
-insert the number (the widget must be in enabled? or normal? (don't remember the name) state to let you insert something to it)
-disable the entry widget
It works like I want... but it doesn't look like I want it to look. Is there any possibility to change Entry widget disabled bg color to normal?
Or maybe is there another way to make such entry box? :S
The way to do it is with the validatecommand and validate options of the entry widget. This scenario is precisely what those features are for.
You say you "don't know how to make it validate every character the user inputs to the entry box". If you set the validate attribute to "key", that will cause your validate command to be called on every keypress.
Unfortunately, this is a somewhat under-documented feature of Tkinter, though it's documented quite well for Tk. Here's a working example which performs some very rudimentary checks:
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# define a command to be called by the validation code. %P
# represents the value of the entry widget if the edit is
# allowed. We want that passed in to our validation comman so
# we can validate it. For more information see
# http://tcl.tk/man/tcl8.5/TkCmd/entry.htm#M7
vcmd = (self.register(self._validate), '%P')
e = tk.Entry(self, validate="key", validatecommand=vcmd)
e.pack()
def _validate(self, P):
# accept the empty string, "." or "-." (to make it possible to
# enter something like "-.1"), or any string that can be
# converted to a floating point number.
try:
if P in (".", "-", "-.", ""):
return True
n = float(P)
return True
except:
self.bell()
return False
app=SampleApp()
app.mainloop()
If you search this site for [tkinter] validatecommand you'll find many other examples.

Categories

Resources