I have created a button which if pressed, directs to the check function which is sort of a validation check for an entry widget for users.
If the first name that the user enters contains anything apart from letters, it should give an invalid check message at coordinate (200, 500) and if last name has an error like that, there should be a message at (200, 700) -- else the screen should proceed to the next screen.
However, if I enter one wrong entry and one right entry, although the message appears at the right place - when I now make the wrong entry right and right entry wrong, instead of the message disappearing from one position, it stays as it is and another message appears at another position (2 messages now, even thought just one entry is wrong)
I am sorry if that is confusing but in simple words, my goal is that the message should only appear on the coordinates where the entry has gone wrong and disappear if the entry is corrected.
How should I alter my code to ensure this?
def check(FName_Entry, LName_Entry):
if not FName_Entry.get().isalpha():
errormsg = Message(root, text = 'Invalid entry', anchor = CENTER)
canvas.create_window(200, 500, anchor=NW, window = errormsg)
#messagebox.showerror('Only letters', 'Only letters are allowed!')
if not LName_Entry.get().isalpha():
errormsg2 = Message(root, text='Invalid entry', anchor=CENTER)
canvas.create_window(200, 700, anchor=NW, window=errormsg2)
if FName_Entry.get().isalpha() and LName_Entry.get().isalpha():
canvas.delete("all")
. . . .
Next_button = Button(root, text="Next", anchor=CENTER, command=lambda: check(FName_Entry, LName_Entry))
Next_button.configure(width=10, bg="black", fg="blue", border=10)
canvas.create_window(920, 450, anchor=NW, window=Next_button)
I'm assuming that this is the part that you get to after selecting whether you are a user or a driver. If we stick with the example that I gave you in your other post:
new config.py
from dataclasses import dataclass, asdict
#to make things more complicated and force you to get better
#create a dataclass of default properties
@dataclass
class Button_dc:
bg: str = "white"
fg: str = "gray20"
font: str = 'Calibri 14 bold'
border:int = 10
#make a dict instance of the dataclass
DefaultButton = asdict(Button_dc())
#make a slightly different instance
UserRoleButton = asdict(Button_dc(font='Calibri 24 bold'))
@dataclass
class Label_dc:
bg: str = "steel blue"
fg: str = "gray20"
font: str = 'Calibri 14 bold'
DefaultLabel = asdict(Label_dc())
@dataclass
class Entry_dc:
bg: str = "white"
fg: str = "gray20"
font: str = 'Calibri 14 bold'
width: int = 30
DefaultEntry = asdict(Entry_dc())
@dataclass
class Message_dc:
bg: str = "red"
fg: str = "white"
font: str = 'Calibri 14 bold'
width: int = 150
DefaultMessage = asdict(Message_dc())
In main.py select the entire user_form
method and paste the entire below in its place
def user_form(self, type, fname=None, lname=None):
self.canvas.delete("all")
self.canvas.create_window(10, 10, anchor='nw', window=tk.Label(self, text="First Name:", **cfg.DefaultLabel))
fname_ent = tk.Entry(self, **cfg.DefaultEntry)
if fname != None:
fname_ent.insert('end', fname)
self.canvas.create_window(120, 10, anchor='nw', window=fname_ent)
self.canvas.create_window(10, 40, anchor='nw', window=tk.Label(self, text="Last Name:", **cfg.DefaultLabel))
lname_ent = tk.Entry(self, **cfg.DefaultEntry)
if lname != None:
lname_ent.insert('end', lname)
self.canvas.create_window(120, 40, anchor='nw', window=lname_ent)
#don't send the entire Entry to the check ~ only the data you want to check
form_next_btn = tk.Button(self, text="next", command=lambda: self.check(type, fname_ent.get(), lname_ent.get()), **cfg.DefaultButton)
self.fnb = self.canvas.create_window(500, 340, anchor='nw', window=form_next_btn)
def check(self, type, fname, lname):
self.canvas.delete(self.fnb)
f = fname.isalpha()
l = lname.isalpha()
if f and l:
self.process(type, fname, lname)
return
if not f:
errormsg = tk.Message(self, text='Invalid entry for first name', **cfg.DefaultMessage)
self.canvas.create_window(10, 90, anchor='nw', window=errormsg)
if not l:
errormsg2 = tk.Message(self, text='Invalid entry for last name', **cfg.DefaultMessage)
self.canvas.create_window(10 if f else 170, 90, anchor='nw', window=errormsg2)
back_btn = tk.Button(self, text="back", command=lambda: self.user_form(type, fname if f else None, lname if l else None), **cfg.DefaultButton)
self.canvas.create_window(500, 340, anchor='nw', window=back_btn)
def process(self, type, fname, lname):
self.canvas.delete("all")
if type is Role.User:
print(f'{fname} {lname} is a user')
elif type is Role.Driver:
print(f'{fname} {lname} is a driver')
aside: Keep in mind that I am not trying, at all, to make your app pretty. I'm just showing you how to make it work. I'm also not writing my best code. I would do all of this completely different. I'm mushing around with your canvas
method and giving you something with a modicum of sanity to it. It seems to me that the entire reason you are using canvas is just so you can have a rounded rectangle. It hardly seems worth it, but that isn't my decision to make.
However, these concepts do not suck and can be very powerful in the proper context. Lets take the dataclasses
, for instance. Imagine if you actually wanted to do the below. You end up with a fully customized Text
widget and a very simple way to change only what you need to change while maintaining everything else you assigned. The dataclasses
that I wrote for you are little example concepts. You can bring those concepts much further.
@dataclass
class Text_dc:
background: str = Theme.Topcoat # main element
foreground: str = Theme.Flat
borderwidth: int = 0
selectbackground: str = Theme.Primer # selected text
selectforeground: str = Theme.Accent
selectborderwidth: int = 0 # doesn't seem to do anything ~ supposed to be a border on selected text
insertbackground: str = Theme.Hilight # caret
insertborderwidth: int = 0 # border
insertofftime: int = 300 # blink off millis
insertontime: int = 600 # blink on millis
insertwidth: int = 2 # width
highlightbackground:int = Theme.Topcoat # inner border that is activated when the widget gets focus
highlightcolor: int = Theme.Topcoat # color
highlightthickness: int = 0 # thickness
cursor: str = 'xterm'
exportselection: int = 1
font: str = 'calibri 14'
width: int = 16 # characters ~ often this is ignored as Text gets stretched to fill its parent
height: int = 8 # lines ~ " " " " "
padx: int = 2
pady: int = 0
relief: str = 'flat'
wrap: str = 'word' # "none", 'word'
spacing1: int = 0 # space above every line
spacing2: int = 0 # space between every wrapped line
spacing3: int = 0 # space after return from wrap (paragraph)
state: str = 'normal' # NORMAL=Read/Write | DISABLED=ReadOnly
tabs: str = '4c'
takefocus: int = 1
undo: bool = True
xscrollcommand: Callable = None
yscrollcommand: Callable = None
#common difference
NoWrap = asdict(Text_dc(**{'wrap':'none'}))
NWReadOnly = asdict(Text_dc(**{'wrap':'none','state':'disabled'}))
ReadOnly = asdict(Text_dc(**{'state':'disabled'}))
DefaultText = asdict(Text_dc())
You can use that concept in a different way. Let's assume that you wanted a completely custom Text
widget and you intended to do more than just restyle it. The below is a quick and dirty example
class ScriptEditor(tk.Text):
def __init__(self, master, **kwargs):
tk.Text.__init__(self, master, **asdict(Text_dc(font='Consolas 14', **kwargs)))
def syntax_highlight(self):
pass
#all the other **kwargs are defaulted internally,
#but we want to adjust the size for this instance
editor = ScriptEditor(root, width=120, height=80)