Validate Email in Entry widget content more than once - tkinter - python-3.x

I have a code that correctly validates email, but this happens only once. So if the email is invalid, it wont let me validate it again. Is there a way how to validate it over and over?
import tkinter as tk
from tkinter import *
from tkinter import ttk
import re
class Test(tk.Frame):
def __init__(self, root):
self.root = root
self.root.geometry("{}x{}+250+150".format(500, 500))
self.entry()
def testAlphaValue(self, value):
if value.isalpha():
return True
else:
return False
def testEmail(self, email):
regex = '^[a-z0-9]+[\._]?[a-z0-9]+[#]\w+[.]\w{2,3}$'
wdg = self.wdgLst
if(re.search(regex, email)):
return wdg.configure(text='Email is valid')
else:
return wdg.configure(text='Email is invalid')
def entry(self):
self.formFrame = LabelFrame(self.root, bg='grey', bd=1)
self.formFrame.place(x=50, y=50, width=400, height=400)
regEmail = self.root.register(self.testEmail)
regAlpha = self.root.register(self.testAlphaValue)
nameEnt = Entry(self.formFrame)
nameEnt.config(validate="key", validatecommand=(regAlpha, '%S'))
nameEnt.grid(row=0, column=1)
emailEnt = Entry(self.formFrame)
emailEnt.config(validate="focusout", validatecommand=(regEmail, '%P'))
emailEnt.grid(row=0, column=0)
emailLbl = Label(self.formFrame, text='Email', font=("Helvetica", 8))
emailLbl.grid(row=1, column=0)
self.wdgLst = emailLbl
root=tk.Tk()
test = Test(root)
root.mainloop()
Thank you!

Here is a working code for ya which validates email on focus-out event (as you wanted) - (must read the reason with explanation below the code)
import tkinter as tk
from tkinter import *
from tkinter import ttk
import re
class Test(tk.Frame):
def __init__(self, root):
self.root = root
self.root.geometry("{}x{}+250+150".format(500, 500))
self.entry()
def testAlphaValue(self, value):
if value.isalpha():
return True
else:
return False
def checkEmail(self, val):
if re.search(regex, val):
self.wdgLst.configure(text='Email is valid')
return True
else:
self.wdgLst.configure(text='Email is Invalid')
return False
def entry(self):
self.formFrame = LabelFrame(self.root, bg='grey', bd=1)
self.formFrame.place(x=50, y=50, width=400, height=400)
regEmail = self.root.register(self.checkEmail)
regAlpha = self.root.register(self.testAlphaValue)
nameEnt = Entry(self.formFrame)
nameEnt.config(validate="key", validatecommand=(regAlpha, '%S'))
nameEnt.grid(row=0, column=1)
emailEnt = Entry(self.formFrame)
emailEnt.config(validate="focusout", validatecommand=(regEmail, '%P'))
emailEnt.grid(row=0, column=0)
emailLbl = Label(self.formFrame, text='Email', font=("Helvetica", 8))
emailLbl.grid(row=1, column=0)
self.wdgLst = emailLbl
root=tk.Tk()
regex = '^[a-z0-9]+[\._]?[a-z0-9]+[#]\w+[.]\w{2,3}$'
test = Test(root)
root.mainloop()
***REASON FOR THE ISSUE ***
From what I got to know about the issue, there seems to be a rule which is::
The function registered to the validate command must return either True or False.
In any other case, if the function returns something else,
it DISABLES the validation for the respected widget.
In your code you were making it return wdg.configure(<something here>) which was disabling the validation right after the first run.
You can also use the method suggested by #ShaneLoyd above if you wish to change your approach but if you just want to stick to validate commands, go with the above code.

I can't explain why your code doesn't work except that perhaps the Entry widget must only allow the validate code to run once. This might work for your needs.
import tkinter as tk
from tkinter import *
from tkinter import ttk
import re
class Test(tk.Frame):
def __init__(self, root):
self.root = root
self.root.geometry("{}x{}+250+150".format(500, 500))
self.entry()
def testAlphaValue(self, value):
if value.isalpha():
return True
else:
return False
def testEmail(self, sv):
email = sv.get()
regex = '^[a-z0-9]+[\._]?[a-z0-9]+[#]\w+[.]\w{2,3}$'
wdg = self.wdgLst
if (re.search(regex, email)):
return wdg.configure(text='Email is valid')
else:
return wdg.configure(text='Email is invalid')
def entry(self):
self.formFrame = LabelFrame(self.root, bg='grey', bd=1)
self.formFrame.place(x=50, y=50, width=400, height=400)
sv_email = StringVar()
sv_email.trace("w", lambda name, index, mode,
sv=sv_email: self.testEmail(sv_email))
regAlpha = self.root.register(self.testAlphaValue)
nameEnt = Entry(self.formFrame)
nameEnt.config(validate="key", validatecommand=(regAlpha, '%S'))
nameEnt.grid(row=0, column=1)
emailEnt = Entry(self.formFrame)
emailEnt.config(textvariable=sv_email)
emailEnt.grid(row=0, column=0)
emailLbl = Label(self.formFrame, text='Email', font=("Helvetica", 8))
emailLbl.grid(row=1, column=0)
self.wdgLst = emailLbl
root = tk.Tk()
test = Test(root)
root.mainloop()

Related

tkinter oo - how to iconify a top-level window linking to a button widget

I am working with python 3.8, macos Big Sur.
from tkinter import *
def test(window):
window.iconify()
def onclick():
window = Toplevel()
window.geometry("+300+300")
window.title("child window")
Button(window, text="click", command=lambda: test(window)).pack()
window.mainloop()
root = Tk()
root.title("parent window")
root.geometry("300x200+200+200")
root.resizable(False, False)
root.iconbitmap("tools.ico")
Button(root, text="open child window", command=onclick).pack()
root.mainloop()
I am trying to rewrite the code above as a class, like:
import tkinter as tk
from tkinter import ttk
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('title')
self.root.geometry("300x200+500+250")
self.root.resizable(False, False)
self.interface()
self.root.iconbitmap("tools.ico")
def interface(self):
self.btn = tk.Button(self.root, text="open child window", command=self.onclick).pack()
def onclick(self):
window = tk.Toplevel()
window.geometry('200x100+500+250')
window.title('child window')
self.btn = tk.Button(window, text="click", command=lambda window: self.test(*window)).pack()
window.mainloop()
def test(self, window):
window.iconify()
if __name__ == '__main__':
gui = GUI()
gui.root.mainloop()
however, it ends with
Traceback (most recent call last):
File "/Applications/miniconda3/lib/python3.8/tkinter/__init__.py", line 1892, in __call__
return self.func(*args)
TypeError: <lambda>() missing 1 required positional argument: 'window'
I also have tried binding an iconify event with button, like:
import tkinter as tk
from tkinter import ttk
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('title')
self.root.geometry("300x200+500+250")
self.root.resizable(False, False)
self.interface()
self.root.iconbitmap("tools.ico")
def interface(self):
self.btn = tk.Button(self.root, text="open child window", command=self.onclick).pack()
def onclick(self):
window = tk.Toplevel()
window.geometry('200x100+500+250')
window.title('child window')
self.btn = tk.Button(window, text="click")
self.btn.pack()
self.btn.bind("<Button-1>", self.test(window=window))
window.mainloop()
def test(self, window):
window.iconify()
if __name__ == '__main__':
gui = GUI()
gui.root.mainloop()
just did not work either.
I would appreciate if anyone point out how I could work with lambda: window in the first example code given above.
==2022/8/13==:
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('title')
self.root.geometry("300x200+500+250")
self.root.resizable(False, False)
self.interface()
self.root.iconbitmap("tools.ico")
def interface(self):
btnopen = tk.Button(self.root, text="open child window", command=self.onclick).pack()
def onclick(self):
window = tk.Toplevel()
window.geometry('200x100+500+250')
window.title('child window')
btnclick = tk.Button(window, text="click", command=lambda: self.test(window)).pack()
window.mainloop()
def test(self, window):
window.iconify()
window.deiconify()
if __name__ == '__main__':
gui = GUI()
gui.root.mainloop()
pass with the code above...
a low class mistake I made is that I defined 2 self.btn. Besides, it should work with lambda: self.test(window).
If all you are looking to do is iconify() the window then why not just use that in the command? No need to separate it as its own method. Is this what you are looking for?
import tkinter as tk
from tkinter import ttk
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('title')
self.root.geometry("300x200+500+250")
self.root.resizable(False, False)
self.interface()
#self.root.iconbitmap("tools.ico")
def interface(self):
self.btn = tk.Button(self.root, text="open child window", command=self.onclick)
self.btn.pack()
def onclick(self):
window = tk.Toplevel()
window.geometry('200x100+500+250')
window.title('child window')
self.btn = tk.Button(window, text="click", command=lambda: window.iconify())
self.btn.pack()
if __name__ == '__main__':
gui = GUI()
gui.root.mainloop()

Tkinter buttons not changing back to the correct color after state changing to active

I am making this PDF tool, and I want the buttons to be disabled until a file or files are successfully imported. This is what the app looks like at the launch:
Right after running the callback for the import files button, the active state looks like this:
I want the colors of the buttons to turn maroon instead of the original grey. They only turn back to maroon once you hover the mouse over them. Any thoughts for how to fix this? Here is the callback for the import button:
def import_callback():
no_files_selected = False
global files
files = []
try:
ocr_button['state'] = DISABLED
merge_button['state'] = DISABLED
status_label.pack_forget()
frame.pack_forget()
files = filedialog.askopenfilenames()
for f in files:
name, extension = os.path.splitext(f)
if extension != '.pdf':
raise
if not files:
no_files_selected = True
raise
if frame.winfo_children():
for label in frame.winfo_children():
label.destroy()
make_import_file_labels(files)
frame.pack()
ocr_button['state'] = ACTIVE
merge_button['state'] = ACTIVE
except:
if no_files_selected:
status_label.config(text='No files selected.', fg='blue')
else:
status_label.config(text='Error: One or more files is not a PDF.', fg='red')
status_label.pack(expand='yes')
import_button = Button(root, text='Import Files', width=scaled(20), bg='#5D1725', bd=0, fg='white', relief='groove',
command=import_callback)
import_button.pack(pady=scaled(50))
I know this was asked quite a while ago, so probably already solved for the user. But since I had the exact same problem and do not see the "simplest" answer here, I thought I would post:
Just change the state from "active" to "normal"
ocr_button['state'] = NORMAL
merge_button['state'] = NORMAL
I hope this helps future users!
As I understand you right you want something like:
...
ocr_button['state'] = DISABLED
ocr_button['background'] = '#*disabled background*'
ocr_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
ocr_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
merge_button['state'] = DISABLED
merge_button['background'] = '#*disabled background*'
merge_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
merge_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
...
...
ocr_button['state'] = ACTIVE
ocr_button['background'] = '#*active background*'
ocr_button.unbind('<Enter>')
ocr_button.unbind('<Leave>')
merge_button['state'] = ACTIVE
merge_button['background'] = '#*active background*'
merge_button.unbind('<Enter>')
merge_button.unbind('<Leave>')
...
If there are any errors, since I wrote it out of my mind or something isnt clear, let me know.
Update
the following code reproduces the behavior as you stated. The reason why this happens is how tkinter designed the standart behavior. You will have a better understanding of it if you consider style of ttk widgets. So I would recommand to dont use the automatically design by state rather write a few lines of code to configure your buttons how you like, add and delete the commands and change the background how you like. If you dont want to write this few lines you would be forced to use ttk.Button and map a behavior you do like
import tkinter as tk
root = tk.Tk()
def func_b1():
print('func of b1 is running')
def disable_b1():
b1.configure(bg='grey', command='')
def activate_b1():
b1.configure(bg='red', command=func_b1)
b1 = tk.Button(root,text='B1', bg='red',command=func_b1)
b2 = tk.Button(root,text='disable', command=disable_b1)
b3 = tk.Button(root,text='activate',command=activate_b1)
b1.pack()
b2.pack()
b3.pack()
root.mainloop()
I've wrote this simple app that I think could help all to reproduce the problem.
Notice that the state of the button when you click is Active.
#!/usr/bin/python3
import sys
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Main(ttk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__()
self.parent = parent
self.init_ui()
def cols_configure(self, w):
w.columnconfigure(0, weight=0, minsize=100)
w.columnconfigure(1, weight=0)
w.rowconfigure(0, weight=0, minsize=50)
w.rowconfigure(1, weight=0,)
def get_init_ui(self, container):
w = ttk.Frame(container, padding=5)
self.cols_configure(w)
w.grid(row=0, column=0, sticky=tk.N+tk.W+tk.S+tk.E)
return w
def init_ui(self):
w = self.get_init_ui(self.parent)
r = 0
c = 0
b = ttk.LabelFrame(self.parent, text="", relief=tk.GROOVE, padding=5)
self.btn_import = tk.Button(b,
text="Import Files",
underline=1,
command = self.on_import,
bg='#5D1725',
bd=0,
fg='white')
self.btn_import.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
self.parent.bind("<Alt-i>", self.switch)
r +=1
self.btn_ocr = tk.Button(b,
text="OCR FIles",
underline=0,
command = self.on_ocr,
bg='#5D1725',
bd=0,
fg='white')
self.btn_ocr["state"] = tk.DISABLED
self.btn_ocr.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_merge = tk.Button(b,
text="Merge Files",
underline=0,
command = self.on_merge,
bg='#5D1725',
bd=0,
fg='white')
self.btn_merge["state"] = tk.DISABLED
self.btn_merge.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_reset = tk.Button(b,
text="Reset",
underline=0,
command = self.switch,
bg='#5D1725',
bd=0,
fg='white')
self.btn_reset.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
b.grid(row=0, column=1, sticky=tk.N+tk.W+tk.S+tk.E)
def on_import(self, evt=None):
self.switch()
#simulate some import
self.after(5000, self.switch())
def switch(self,):
state = self.btn_import["state"]
if state == tk.ACTIVE:
self.btn_import["state"] = tk.DISABLED
self.btn_ocr["state"] = tk.NORMAL
self.btn_merge["state"] = tk.NORMAL
else:
self.btn_import["state"] = tk.NORMAL
self.btn_ocr["state"] = tk.DISABLED
self.btn_merge["state"] = tk.DISABLED
def on_ocr(self, evt=None):
state = self.btn_ocr["state"]
print ("ocr button state is {0}".format(state))
def on_merge(self, evt=None):
state = self.btn_merge["state"]
print ("merge button state is {0}".format(state))
def on_close(self, evt=None):
self.parent.on_exit()
class App(tk.Tk):
"""Main Application start here"""
def __init__(self, *args, **kwargs):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_style()
self.set_title(kwargs['title'])
Main(self, *args, **kwargs)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self, title):
s = "{0}".format('Simple App')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
self.destroy()
def main():
args = []
for i in sys.argv:
args.append(i)
kwargs = {"style":"clam", "title":"Simple App",}
app = App(*args, **kwargs)
app.mainloop()
if __name__ == '__main__':
main()

python 3+ with tkinter: modal window not appearing

I'm new to python. I'm trying to set up a GUI with a main window and a modal window to receive the database credentials. The main window has to be in the background when the modal window appears and the user should not interact with it. Actually the application should terminate in case the user fails to provide a valid username and password. I'm presenting part of the code here. What I get is the main window (the modal window does not appear at all) and I can't do anything but shut the program down - not directly; only through visual studio.
'''
import tkinter as tk
class login:
def checkDbCredentials(self, user, password):
if (len(user)<4 or len(password)<4):
return 0
return 1
def ok(self):
us = self.user.get()
pw = self.password.get()
if self.checkDbCredentials(us, pw)==0:
tk.messagebox.showinfo("Error", "Username and Password do not match")
sys.exit(0)
self.dic['user']=us
self.dic['pass']=pw
self.win2.destroy()
def cancel(self, event=None):
self.parent.focus_set()
self.destroy()
def widgets(self):
x = self.win2.winfo_screenwidth()
y = self.win2.winfo_screenheight()
xx = int((x-270)/2)
yy = int((y-85)/2)
geo = "270x85+" + str(xx) + "+" + str(yy)
self.win2.geometry(newGeometry=geo)
self.tempLab= tk.Label(self.win2, text="Username:", pady=5)
self.tempLab1 = tk.Label(self.win2, text="Password:")
self.iuser = tk.Entry(self.win2, textvariable = self.user, width=30)
self.ipass = tk.Entry(self.win2, textvariable = self.password, width=30, show="*")
self.tempLab.grid(column=0, row=0)
self.tempLab1.grid(column=0, row=1)
self.iuser.grid(column=1, row=0)
self.ipass.grid(column=1, row=1)
self.bt = tk.Button(self.win2, text="Submit", command=lambda: self.ok())
self.bt.grid(column=0, row=2, columnspan=2, pady=5)
self.win2.bind("<Return>", self.ok)
self.win2.bind("<Escape>", self.cancel)
def __init__(self, dic, parent):
self.win2 = tk.Toplevel(parent)
self.parent=parent
self.dic = dic
self.user = tk.StringVar()
self.password = tk.StringVar()
self.win2.overrideredirect(1)
self.widgets()
self.win2.transient(parent)
self.win2.grab_set()
self.win2.protocol("WM_DELETE_WINDOW", self.cancel)
self.parent.wait_window(self.win2)
And the main class code is the following:
import tkinter as tk
from tkinter import ttk, X, Y, BOTH
from tkinter import messagebox
import sys
import login
class mainWindow:
def connect(bt, fr):
notImplementedYet()
def notImplementedYet():
msg = tk.messagebox
msg.showinfo("Warning", "Not Implemented yet!")
btX = 20
btY = 5
btSunken = '#aec2c2' # sunken button color
btRaised = '#85adad' # normal button color
rw = 0 # starting row for buttons
cl = 0 # starting column
dbCredentials = {"user":"", "pass":""}
win = tk.Tk()
win.title("Title")
win.geometry(newGeometry="1366x700+00+00")
win.iconbitmap(bitmap="bitmap.ico")
fr1 = tk.Frame(win, width=1366, height=50, background='beige')
fr1.grid(column=cl, row=rw, rowspan=5)
fr2 = tk.Frame(win)
fr2.grid(column=0, row=1)
lb2 = tk.Label(fr2, text="frame 2", background=btRaised)
btConnect = tk.Button(fr1, text="Connect", background = btRaised ,command=lambda: connect(btConnect, fr1), width=btX, height=btY)
btConnect.grid(column=0, row=0)
log = login.login(dbCredentials, win)
win.wait_window(log.win2)
print(dbCredentials)
win.mainloop()
win = mainWindow()
The question is why is the modal window not appearing? I've tried placing the 'wait_window' in the other class as well but it's still not working.

How to bind a validated entry value to a variable?

Based on a answer from an earlier stackoverflow I tried to bind an entry (via entry.get) to a variable. Initially it seemed to work as I can indeed change the value as indicated by a print call in the get_value method. However at the end the variable has not been changed, as is shown by pressing check button. I hope somebody can show what I'm doing wrong.
import tkinter as tk
class Window():
def __init__(self):
tk.Label(master, text ='Fox number').grid(row=0,column=0)
tk.Label(master, text ='Hare number').grid(row=1,column=0)
self.fox_entry=tk.Entry(master,width=5, validate="key")
self.fox_entry['validatecommand'] =\
self.fox_entry.register(self.is_okay),'%P'
self.hare_entry=tk.Entry(master, width=5, validate="key")
self.hare_entry['validatecommand'] =\
self.hare_entry.register(self.is_okay),'%P'
self.fox_entry.grid(row=0, column=1)
self.hare_entry.grid(row=1, column=1)
def is_okay(self, P):
try:
if P == '' or int(P) >= 0:
return True
except:
return False
class Ecosystem():
def __init__(self):
self.foxnumber = 100
self.harenumber = 10
self.inputvalue = None
def animal_entries(self):
def input_user(entry):
def get_value(event):
self.inputvalue = entry.get()
print(self.inputvalue)
return self.inputvalue
entry.bind('<Return>', get_value)
return self.inputvalue
my_win.fox_entry.insert(0,self.foxnumber)
my_win.hare_entry.insert(0,self.harenumber)
self.inputvalue = self.foxnumber
self.foxnumber = input_user(my_win.fox_entry)
self.inputvalue = self.harenumber
self.harenumber = input_user(my_win.hare_entry)
def write(self):
print('hares: ', self.harenumber, 'foxes: ',self.foxnumber)
master = tk.Tk()
my_win = Window()
my_ecosystem = Ecosystem()
my_ecosystem.animal_entries()
tk.Button(master, text = 'check',command=my_ecosystem.write).grid(row=2, column=0)
master.mainloop()

Return Value from TkInter Entry with Button

Python novice, here. I've noticed there are a lot of questions around the topic of returning values from a TkInter function, but none of the solutions seem to solve my issue.
I can successfully print self.e1path to the shell from within getPath.submit, but I cannot return it to the rest of my code. I'm using a print statement outside of the class to test whether I've successfully returned the CSV path.
from tkinter import *
import tkinter as tk
class getPath(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.label1 = tk.Label(self, text="CSV Path").grid(row=0, column=0)
self.e1 = tk.Entry(self, width=50)
self.e1Grid = self.e1.grid(row=0, column=1)
self.browse = tk.Button(self, text='Browse', command=self.getCSV).grid(row=0, column=2)
self.submit = tk.Button(self, text='Submit', command=self.submit).grid(row=1, column=1)
def getCSV(self):
self.fileName = filedialog.askopenfilename( filetypes = (('Comma Separated Values', '*.csv'), ('All Files', '*.*')), title = "Choose a CSV File")
self.e1.insert(10, self.fileName)
def submit(self):
self.e1Path = self.e1.get()
return self.e1Path
app = getPath()
app.mainloop()
print(app)
I figured it out! I needed to add a self.destroy() to the submit function. This stopped the mainloop and let me call on self.e1path outside of the function using app.e1path. New code:
from tkinter import *
import tkinter as tk
class getPath(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.label1 = tk.Label(self, text="CSV Path").grid(row=0, column=0)
self.e1 = tk.Entry(self, width=50)
self.e1Grid = self.e1.grid(row=0, column=1)
self.browse = tk.Button(self, text='Browse', command=self.getCSV).grid(row=0, column=2)
self.submit = tk.Button(self, text='Submit', command=self.submit).grid(row=1, column=1)
def getCSV(self):
self.fileName = filedialog.askopenfilename( filetypes = (('Comma Separated Values', '*.csv'), ('All Files', '*.*')), title = "Choose a CSV File")
self.e1.insert(10, self.fileName)
def submit(self):
self.e1Path = self.e1.get()
self.destroy()
app = getPath()
app.mainloop()
print(app.e1Path)

Resources