I have seen several questions on tkinter entry validation here but each one seems to stick to validate="key" option.
While this is great for interactive validation, what i want is a "focusout" validation.
More particularly I am looking to validate an email field. Here's the code I have tried so far but it doesn't work.
import Tkinter as tk
import re
master = tk.Tk()
def validateEmail(P):
x = re.match(r"[^#]+#[^#]+\.[^#]+", P)
return (x != None)
vcmd = (master.register(validateEmail), '%P')
emailentry = tk.Entry(master, validate="focusout", validatecommand=vcmd)
emailentry.pack()
b = tk.Button(master, text="Login")
b.pack()
tk.mainloop()
Any ideas on how to validate email entry please ?
%S represents the string being inserted, if any. This is only meaningful for validation on text insertion. When the widget loses focus, no character is being inserted so this parameter will always be an empty string. Since it is an empty string, it will always fail your validation.
You should use %P instead, which represents the whole string.
Also, strictly speaking, the validation function should return a boolean rather than an object. You should save the result of the match in a variable, then return something like return (match is not None)
Related
I am trying to create an entry with a very special validation. For that, I'm playing dirty with validatecommand. However, I got a problem:
When something in the entry is deleted, I can't tell if it was done with the delete or backspace key (and tutorial pages like this: https://www.pythontutorial.net/tkinter/tkinter-validation/ no substitution is indicated provide that information).
So, I decided to add a bind. The function that I link returns "break" and must take care of removing a character and inserting a space in its place.
The problem, as the title says, is that validatecommand even validates entry edits made with the insert and delete methods.
To avoid this I considered disabling validation (which always returns True) while I make the corresponding edits. But this could cause other entries not to be validated.
Is there a way to skip that validation when programmatically editing an entry?
I leave you this code so that you have some basis to help me:
from functools import partial
class ChrFormat:
def __init__(self):
self.charvalidators = []
def register_in(self, widget):
widget.config(validate="key", validatecommand=(widget.register(partial(self, widget)), "%d", "%i", "%P", "%s", "%S"))
def add(self, obj):
self.charvalidators.append(obj)
def __call__(self, widget, accion, index, new_text, old_text, char):
accion = int(accion)
index = int(index)
if(len(char) == 1):
if(accion == 1):
if(index < self.width):
for validator in self.charvalidators[index:]:
if(not isinstance(validator, str)):
break
index += 1
else:
return False
if(validator(char)):
widget.delete(index)
widget.insert(index, char)
widget.icursor(index + 1)
return (accion != 1)
def apply(self):
self.width = len(self.charvalidators)
self.null = "".join((part if(isinstance(part, str)) else " ") for part in self.charvalidators)
fecha = ChrFormat()
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.add("-")
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.add("-")
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.apply()
from tkinter import ttk
import tkinter as tk
root = tk.Tk()
sv = tk.StringVar()
entrada = ttk.Entry(textvariable=sv)
entrada.pack()
fecha.register_in(entrada)
sv.set(fecha.null)
I think I didn't explain myself well, sorry. What I'm looking for is that when the user presses backspace, it deletes a number and puts a space in its place. And something similar with delete. But I need to know which side of the cursor to put that space on.
Obviously, natural validation is the right thing for this, maybe do the validation through binds.
For those who know a bit about Clipper programming languaje, I want to mimic what happens when a picture is placed, such as '#r 999.999'. I would post a video, but I'm not in a good time to record and I didn't find any videos to prove it.
Is there a way to skip that validation when programmatically editing an entry?
The simplest solution is to set the validate option to "none" before making the edits. You then can turn the validation back on via after_idle as documented in the official tk documentation
widget.configure(validate="none")
widget.delete(index)
widget.insert(index, char)
widget.icursor(index + 1)
widget.after_idle(lambda: widget.configure(validate="key"))
In the Entry widget, I tried to restrict values to only hexadecimal using validatecommand and a function to verify if the entry value is in 'abcdef0123456789'. The problem lies in the fact that "CTRL+V" (Paste) doesn't work when I use it to insert value. There is no problem with "CTRL+C" and "CTRL+X" (Copy and Cut).
Is that a way to restrict the entry to hexadecimal value and in same time allow the use of CTRL-V to Paste/insert value?
Is that a way to restrict the entry to hexadecimal value and in same time allow the use of CTRL-V to Paste/insert value?
Yes. Arguably the simplest method is to use the validatecommand to check that all of the characters in the widget are hex digits. The validatecommand doesn't care how the characters are entered (typing vs cut/paste).
In the following example, '%P' tells tkinter to pass the new value of the entry widget if the edit is allowed. We can then use the re module to see if the new value is composed of only zero or more hex digits.
import tkinter as tk
import re
def validate(possible_new_value):
if re.match(r'^[0-9a-fA-F]*$', possible_new_value):
return True
return False
root = tk.Tk()
entry = tk.Entry(root, validate="key",
validatecommand=(root.register(validate), '%P'))
entry.pack(padx=20, pady=20)
root.mainloop()
I need to fetch one number from a column
cursor.execute('''SELECT vacas FROM animales''')
cantidad1 = cursor.fetchone()
Then I need this number to be shown in a Tkinter Label:
cantidad = Label (ventana, textvariable=cantidad1).grid(row=1, column=3)
And I have a refresh button in order to update the data.
ref = Button (ventana, text="Refresh", command=update )
The problem is that the Label is always blank, even when I press button and call Update():
Here is the complete code:
cantidad1 = 0
ventana = Tk()
cursor = db.cursor()
def update():
cursor.execute('''SELECT vacas FROM animales''')
cantidad1 = cursor.fetchone()
print (cantidad1[0]) #The number shown in command is right, but blank in tkinter.
ref = Button (ventana, text="Refresh", command=update )
ref.grid(row=3, column=2)
cantidad = Label (ventana, textvariable=cantidad1).grid(row=1, column=3)
ventana.mainloop()
https://imgur.com/AvsNAuL "Screenshot tkinter blank"
Using textvariable in Tkinter can be handy, but it requires thinking a bit in Tcl/Tk style instead of Python style. You may want to read up on it I'm not sure where the best docs are, but Entry and The Variable Classes in the old book are probably a good place to start.
Anyway, a textvariable binding has to refer to a Tcl variable, like a StringVar. Somewhere before creating that Label with textvariable=cantidad1 you need to do something like this:
cantidad1 = StringVar('0')
And then, instead of this:
cantidad1 = cursor.fetchone()
… you have to do this:
cantidad1.set(cursor.fetchone())
What you're doing is changing the name cantidad1 to refer to the new result, but what you need to do is leave it referring to the StringVar and change the value of the StringVar, so Tk can see it and update the label.
While we're at it, I think you actually want something like cursor.fetchone()[0]; otherwise you're trying to use a row (probably a list or other sequence, not a string).
Finally, you could use an IntVar here. That would allow you to initialize it to 0 instead of '0', but then of course you have to set it to int(spam) instead of just spam, and you may need some error handling to deal with what happens if the database returns Null.
If all of this is Greek to you (or, worse, Tcl), the other option is to just not use Tk variables:
cantidad = Label(ventana, text='0').grid(row=1, column=3)
Notice that the difference here is that I set the initial text, rather than setting a textvariable.
So now, every time you fetch new data, you'll have to manually update (re-config) the label's text, like this:
cantidad1 = ... # code that just gets a normal Python string
cantidad.config(text=cantidad1)
This is much less idiomatic Tk code, and arguably less idiomatic Tkinter code—but it's a lot more Pythonic, so it may be easier for you to read/debug/write. (Notice that the first line is exactly what you instinctively wrote, which was wrong with your original design, hence your question, but would be right with the explicit-update design.)
—-
There are other problems in the code that I haven’t fixed. For example, the grid method on widgets doesn’t return the widget, it returns None. You’re doing it right for ref, but not for cantidad. Your original code never actually referred to cantidad, so that wasn't a problem, but if you, e.g., switch to using manual updates instead of automatic variables, you're going to get a confusing exception about calling configure on None.
Question
Why is my random ascii character selector function outputting fours, and what is the significance of the number four in this context? Why am I not recieving an error message?
Remember, the question is not about how to solve the issue, it is about why that particular number was output.
Background and Code
I am trying to creating a basic email client. I thought that it would be cool for my password box to show random characters instead of the obvious *. So, I created a function which chose a random ascii letter.
import random
import string
def random_char():
char_select = random.randrange(52)
char_choice = string.ascii_letters[char_select]
return char_choice
When I run this in an interactive terminal, it spits out a random letter. But, when I run it through my widget
self.Password = Entry (self, show = lambda: random_char())
I am met with a bunch of fours.
Extra Credit
If you have the time, please visit my related question, How to have a Tkinter Entry box repeat a function each time a character is inputted?
The show parameter accepts a value not a callback. Tkinter is taking your callback object and trying to convert it to a string and that is what you get when you type in the Entry box.
Instead you can re-configure your Entry after you type by using binding:
def key(event):
entry.configure(show = random_char())
entry = tk.Entry (self)
entry.pack()
entry.bind("<Key>", key)
EDIT
Bryan Oakley is correct in that this will change all the characters to the same single random character as you type. Showing different random characters as you type is not the way you are supposed to use the Entry widget. You can try something like:
def key(event):
global real_password
global garbage
current_len = len(v.get())
if event.char and event.char in string.ascii_letters:
real_password += event.char
garbage += random_char()
garbage = garbage[:current_len]
v.set(garbage)
v = tk.StringVar()
real_password = ""
garbage = ""
entry = tk.Entry (self, textvariable = v)
entry.pack()
entry.bind("<KeyRelease>", key)
Of course there are lots of limitations, the last character typed is changed when the key is released not when is pressed, so you have to type fast :) , there is not control over the cursor movement keys etc. But anyway it was fun trying.
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.