Working with Class#
Trimming Down#
When a message does not show there is an annoying yellow bar showing where a message would be. Blend the Label into the background leaving only the foreground colours to show, or hide the Label.#
First move the colour style clauses from the init function to the construct method in StringEntry remembering to add the lower and upper limit colours, then remove the same clauses in IntegerEntry and FloatEntry.
Remove the feedback information for cb_opt in modify and toggle_opt. Remove the passed argument mess_text, but still keep mess_lbl as it provides confirmation that the input has succeeded.
The method end_input, found in IntegerEntry, int(self.out_var.get())
is not needed as out_var has been changed to an IntVar, the same applies to
float(self.out_var.get()) in FloatEntry, end_input is now duplicated
and can now be removed.
The limits method has been changed to tie in with labelframe rather than parent:
class StringEntry:
def __init__(self, parent,lf_text,def_text="",colour='brown',mod=False):
....
#self.mess_text = mess_text
....
self.construct(colour)
def construct(self,colour):
## insert colour style clauses ##
.........
self.mess_lbl = Label(self.lf,style='brown.TLabel')
.....
self.cb_opt['text'] = lf_text # +'\n Check to prevent editing '
....
self.cb_opt['text'] = lf_text # +'\n Check to modify '
....
class IntegerEntry(StringEntry):
def __init__(self,parent,lf_text,l_limit,u_limit,def_text="",colour='brown',mod=False):
.......
StringEntry.__init__(self,parent,lf_text,def_text,colour,mod)
....
if self.l_limit < self.out_var.get() < self.u_limit:
....
elif self.l_limit > self.out_var.get():
....
class FloatEntry(IntegerEntry):
def __init__(self,parent,lf_text,l_limit,u_limit,def_text="",colour='brown',mod=False):
......
# remove method end_input
Show/Hide Code entry_class_4.py
"""String, Integer and Float Entry classes, modified"""
from tkinter import Tk, StringVar, IntVar, DoubleVar
from tkinter.ttk import Entry, Style, Label, LabelFrame, Button, Frame,\
Checkbutton
class StringEntry:
"""String class for entry
rationalised with integer and float classes
Parameters
----------
parent : str
parent handle
lf_text : str
text on LabelFrame
def_inp : str
default text
colour : str
frame colour
mod : bool
enable or disable state switch
Returns
-------
string
"""
def __init__(self, parent, lf_text, def_inp="", colour='brown', mod=False):
self.parent = parent
self.lf_text = lf_text
# self.mess_text = mess_text
self.mod = mod
self.ent0 = None # for entry
self.cb_opt = None # for check option
self.out_var = StringVar()
self.out_var.set(def_inp)
self.construct(colour) # changed
def construct(self, colour): # changed
"""construct of colour style
Parameters
----------
colour : str
frame colour
Returns
-------
None
"""
self.farbe = farbe = {'blue': 'light blue', 'brown': 'brown1',
'green': 'light green', 'pink': '#EAAFBF'}
colour = colour if colour in farbe else 'brown'
self.colour = colour
st1 = Style()
st1.theme_use('default')
st1.configure(colour + '.TLabelframe', background='#C9B99B')
st1.configure(colour + '.TLabelframe.Label', background=farbe[colour])
st1.configure(colour + '.TCheckbutton', background=farbe[colour])
st1.configure('brown.TLabel', background='#EDEF77')
st1.configure('lowr.TLabel', background='lightblue') # new
st1.configure('upr.TLabel', background='red') # new
self.lf0 = LabelFrame(self.parent, text=self.lf_text,
style=self.colour + '.TLabelframe')
self.lf0.grid(column=0, row=0, padx=10, pady=10)
self.messlbl = Label(self.lf0, style='brown.TLabel') # removed text
self.messlbl.grid(row=2, column=0, pady=10, padx=10)
self.make_entry()
def make_entry(self):
"""construct of Entry
Parameters
----------
None
Returns
-------
None
"""
vcmd = self.lf0.register(self.is_okay)
self.ent0 = ent0 = Entry(self.lf0, validate='key',
validatecommand=(vcmd, '%P', '%S', '%i'),
textvariable=self.out_var)
ent0.bind("<Return>", self.end_input)
ent0.grid(row=1, column=0, padx=10)
ent0.focus()
if self.mod in (True, False):
self.modify()
def modify(self):
"""construct of state switch
Parameters
----------
None
Returns
-------
None
"""
lf_text = self.lf_text
# entry disabled until checkbox is ticked
self.cb_opt = Checkbutton(self.lf0, command=self.toggle_opt,
style=self.colour + '.TCheckbutton')
self.lf0['labelwidget'] = self.cb_opt
if self.mod:
self.ent0.state(['!disabled'])
self.cb_opt.state(['!selected'])
self.cb_opt['text'] = lf_text # +'\n Check to prevent editing '
self.ent0.focus()
else:
self.ent0.state(['disabled'])
self.cb_opt.state(['selected'])
self.cb_opt['text'] = lf_text # +'\n Check to modify '
def toggle_opt(self):
"""state switch logic
Parameters
----------
None
Returns
-------
None
"""
lf_text = self.lf_text
# state of entry controlled
# by the state of the check button in Option frame label widget
if self.cb_opt.instate(['selected']):
self.ent0.state(['disabled'])
self.cb_opt['text'] = lf_text # +'\n Check to modify '
else:
self.ent0.state(['!disabled']) # enable option
self.cb_opt['text'] = lf_text # changed
self.ent0.focus()
def end_input(self, evt):
"""limit on string
Parameters
----------
evt : str
bind handle
Returns
-------
None
"""
if len(self.out_var.get()) > 5:
self.messlbl['text'] = "That's OK"
else:
self.messlbl['text'] = "Need at least 6 characters"
def is_okay(self, text, inp, ind):
""" validation function
Parameters
----------
text : str
text if allowed
inp : str
current input
Returns
-------
boolean
"""
ind = int(ind)
print(ind)
if (inp.isalnum() or inp in (",", ".", "'", " ")) and ind > 0:
return True
else:
return bool((text.isupper() or text == "") and ind == 0)
class IntegerEntry(StringEntry):
"""Integer class for entry
Parameters
----------
parent : str
parent handle
lf_text : str
text on LabelFrame
l_limit : int
lower limit
u_limit : int
upper limit
def_inp : str
default text
colour : str
frame colour
mod : bool
enable or disable state switch
Returns
-------
integer
"""
def __init__(self, parent, lf_text, l_limit, u_limit, def_inp="",
colour='brown', mod=False):
self.parent = parent
self.lf_text = lf_text
# self.mess_text = mess_text
self.mod = mod
self.colour = colour
StringEntry.__init__(
self,
parent,
lf_text,
def_inp,
colour,
mod) # mess_text
self.l_limit = l_limit
self.u_limit = u_limit
self.out_var = IntVar()
self.out_var.set(def_inp)
self.construct(colour) # changed
self.limits()
self.make_entry() # added
def limits(self):
"""limit logic
Parameters
----------
None
Returns
-------
None
"""
self.ulab = Label(self.lf0, text=str(self.u_limit) + " upper limit",
style='brown.TLabel')
self.ulab.grid(row=0, column=1, padx=10)
self.llab = Label(self.lf0, text=str(self.l_limit) + " lower limit",
style='brown.TLabel')
self.llab.grid(row=1, column=1, padx=10) # changed position
def end_input(self, evt):
"""limit on integer, float
Parameters
----------
evt : str
bind handle
Returns
-------
None
"""
self.ulab['style'] = 'brown.TLabel'
self.llab['style'] = 'brown.TLabel'
if self.l_limit < self.out_var.get() < self.u_limit:
self.messlbl['text'] = "That's OK"
elif self.l_limit >= self.out_var.get():
self.messlbl['text'] = "Input below or at lower limit"
self.llab['style'] = 'lowr.TLabel'
else:
self.messlbl['text'] = "Input above or at upper limit"
self.ulab['style'] = 'upr.TLabel'
def is_okay(self, text, inp, _ind):
""" validation function
Parameters
----------
text : str
text if allowed
inp : str
current input
Returns
-------
boolean
"""
if text in ("", "-"):
return True
try:
int(text)
except ValueError:
return False
return True
class FloatEntry(IntegerEntry):
"""Float class for entry
Parameters
----------
parent : str
parent handle
lf_text : str
text on LabelFrame
l_limit : float
lower limit
u_limit : float
upper limit
def_inp : str
default text
colour : str
frame colour
mod : bool
enable or disable state switch
Returns
-------
float
"""
def __init__(self, parent, lf_text, l_limit, u_limit, def_inp="",
colour='brown', mod=False): # mess_text,
self.parent = parent
self.lf_text = lf_text
# self.mess_text = mess_text
self.mod = mod
self.colour = colour
self.l_limit = l_limit
self.u_limit = u_limit
IntegerEntry.__init__(self, parent, lf_text, l_limit, u_limit,
def_inp=None, colour='brown', mod=False)
self.out_var = DoubleVar()
self.out_var.set(def_inp)
self.construct(colour)
self.make_entry() # changed
self.limits()
''' removed
def end_input(self,evt):
self.ulab['style'] = 'brown.TLabel'
self.llab['style'] = 'brown.TLabel'
if self.l_limit < self.out_var.get() < self.u_limit:
self.messlbl['text'] = "That's OK"
elif self.l_limit > self.out_var.get():
self.messlbl['text'] = "Input below lower limit"
self.llab['style'] = 'lowr.TLabel'
else:
self.messlbl['text'] = "Input above upper limit"
self.ulab['style'] = 'upr.TLabel'
'''
def is_okay(self, text, inp, _ind):
""" validation function
Parameters
----------
text : str
text if allowed
inp : str
current input
Returns
-------
boolean
"""
if text in ("", "-", ".", "-."):
return True
try:
float(text)
except ValueError:
return False
return True
if __name__ == "__main__":
root = Tk()
fra0 = Frame(root)
fra0.grid(row=0, column=0)
'''
LF_TEXT = 'Beer Type'
DEF_INP = 'Pilsner'
COLOUR = 'green'
MOD = True
v = StringEntry(fr,LF_TEXT,DEF_INP,COLOUR,MOD)
'''
LF_TEXT = 'Number of Coils'
DEF_INP = 10
L_LIMIT = 1
U_LIMIT = 100
v = IntegerEntry(fra0, LF_TEXT, L_LIMIT, U_LIMIT, DEF_INP) # ,COLOUR,MOD)
'''
LF_TEXT = 'Beer Strength v/v % alcohol'
DEF_INP = 5.5
L_LIMIT = 0.5
U_LIMIT= 10.5
v = FloatEntry(fr,LF_TEXT,L_LIMIT,U_LIMIT,DEF_INP,COLOUR,MOD)
'''
b2 = Button(root, text='Click after selection',
command=lambda: print(v.out_var.get(), v.messlbl['text']))
b2.grid(row=2, column=0)
root.mainloop()
Adding Class Inheritance to Base Class#
Use the LabelFrame class as the parent class for StringEntry with a
touch of the supers thrown in for good measure. In essence we inherit all
the methods and parameters from LabelFrame, where the hook
self.lf was used just becomes self, LabelFrame no longer needs to be
called in the construct method. Parameters already
incorporated in the three super calls can be deleted (commented out to show
immediate changes). To enable the style changes a call to self['style']
is made just after configure in the construct method.
As you can see, the original composition call to LabelFrame has been changed to an inheritance call, in other words our widget changed from a "has a" to a "is a" type of relationship with the LabelFrame widget. As it stands it is still a LabelFrame widget hence it can be displayed using normal layout management within our main part or from a calling program.
Within an application it would be better if the widgets all appeared the same size. Call the dimensions from the widgets themselves within __main__. Just from this application we can see how more complex widgets might be created.
Show/Hide Code entry_class_5.py
"""String, Integer and Float Entry classes, using super,
inherit from LabelFrame
"""
from tkinter import Tk, StringVar, IntVar, DoubleVar
from tkinter.ttk import Entry, Style, Label, LabelFrame, Button, Frame, \
Checkbutton
class StringEntry(LabelFrame): # changed
"""String class for entry
rationalised with integer and float classes
super
Parameters
----------
parent : str
parent handle
lf_text : str
text on LabelFrame
def_inp : str
default text
colour : str
frame colour
mod : boolean
enable or disable state switch
Returns
-------
string
"""
def __init__(self, parent, lf_text, def_inp="", colour='brown', mod=False):
self.lf_text = lf_text
super().__init__(parent, text=lf_text) # added
self.mod = mod
self.ent0 = None # for entry
self.cb_opt = None # for check option
self.out_var = StringVar()
self.out_var.set(def_inp)
self.construct(colour)
def construct(self, colour):
"""construct of colour style
Parameters
----------
colour : str
frame colour
Returns
-------
None
"""
self.farbe = farbe = {'blue': 'light blue', 'brown': 'brown1',
'green': 'light green', 'pink': '#EAAFBF'}
colour = colour if colour in farbe else 'brown'
self.colour = colour
st1 = Style()
st1.theme_use('default')
st1.configure(colour + '.TLabelframe', background='#C9B99B')
st1.configure(colour + '.TLabelframe.Label', background=farbe[colour])
st1.configure(colour + '.TCheckbutton', background=farbe[colour])
st1.configure('brown.TLabel', background='#EDEF77')
st1.configure('lowr.TLabel', background='lightblue')
st1.configure('upr.TLabel', background='red')
# self.lf1 = Labelframe(self.fr, text=self.lf_text,
# style=self.colour+'.TLabelframe')
# self.lf1.grid(column=0,row=0,padx=10, pady=10)
self['style'] = self.colour + '.TLabelframe'
self.messlbl = Label(self, style='brown.TLabel') # self.lf1
self.messlbl.grid(row=2, column=0, pady=10, padx=10)
self.make_entry()
def make_entry(self):
"""construct of Entry
Parameters
----------
None
Returns
-------
None
"""
vcmd = self.register(self.is_okay)
self.ent0 = ent0 = Entry(self, validate='key',
validatecommand=(
vcmd, '%P', '%S', '%i'), # self.lf1
textvariable=self.out_var)
ent0.bind("<Return>", self.end_input)
ent0.grid(row=1, column=0, padx=10)
ent0.focus()
if self.mod in (True, False):
self.modify()
def modify(self):
"""construct of state switch
Parameters
----------
None
Returns
-------
None
"""
# entry disabled until checkbox is ticked
self.cb_opt = Checkbutton(self, command=self.toggle_opt, # self.lf1
style=self.colour + '.TCheckbutton')
self['labelwidget'] = self.cb_opt # self.lf1[
if self.mod:
self.ent0.state(['!disabled'])
self.cb_opt.state(['!selected'])
self.cb_opt['text'] = self.lf_text
self.ent0.focus()
else:
self.ent0.state(['disabled'])
self.cb_opt.state(['selected'])
self.cb_opt['text'] = self.lf_text
def toggle_opt(self):
"""state switch logic
Parameters
----------
None
Returns
-------
None
"""
# state of entry controlled
# by the state of the check button in Option frame label widget
if self.cb_opt.instate(['selected']):
self.ent0.state(['disabled'])
self.cb_opt['text'] = self.lf_text
else:
self.ent0.state(['!disabled'])
self.cb_opt['text'] = self.lf_text
self.ent0.focus()
def end_input(self, _evt):
"""limit on string
Parameters
----------
evt : str
bind handle
Returns
-------
None
"""
if len(self.out_var.get()) > 5:
self.messlbl['text'] = "That's OK"
else:
self.messlbl['text'] = "Need at least 6 characters"
def is_okay(self, text, inp, ind):
""" validation function
Parameters
----------
text : str
text if allowed
inp : str
current input
Returns
-------
boolean
"""
ind = int(ind)
if (inp.isalnum() or inp in (",", ".", "'", " ")) and ind > 0:
return True
else:
return bool((text.isupper() or text == "") and ind == 0)
class IntegerEntry(StringEntry):
"""Integer class for entry
super
Parameters
----------
parent : str
parent handle
lf_text : str
text on LabelFrame
l_limit : int
lower limit
u_limit : int
upper limit
def_inp : str
default text
colour : str
frame colour
mod : str
enable or disable state switch
Returns
-------
integer
"""
def __init__(self, parent, lf_text, l_limit, u_limit, def_inp="",
colour='brown', mod=False):
'''
#self.parent = parent
#self.lf_text = lf_text
#self.mod = mod
#self.colour = colour
'''
super().__init__(parent, lf_text, def_inp, colour, mod) # added
# StringEntry.__init__(self,fr,lf_text,def_inp,colour,mod)
self.l_limit = l_limit
self.u_limit = u_limit
self.out_var = IntVar()
self.out_var.set(def_inp)
self.construct(colour)
self.limits()
self.make_entry()
def limits(self):
"""limit logic
Parameters
----------
None
Returns
-------
None
"""
self.ulab = Label(self, text=str(self.u_limit) + " upper limit",
style='brown.TLabel') # self.lf1
self.ulab.grid(row=0, column=1, padx=10)
self.llab = Label(self, text=str(self.l_limit) + " lower limit",
style='brown.TLabel') # self.lf1
self.llab.grid(row=1, column=1, padx=10)
def end_input(self, evt):
"""limit on integer, float
Parameters
----------
evt : str
bind handle
Returns
-------
None
"""
self.ulab['style'] = 'brown.TLabel'
self.llab['style'] = 'brown.TLabel'
if self.l_limit < self.out_var.get() < self.u_limit:
self.messlbl['text'] = "That's OK"
elif self.l_limit >= self.out_var.get():
self.messlbl['text'] = "Input below or at lower limit"
self.llab['style'] = 'lowr.TLabel'
else:
self.messlbl['text'] = "Input above or at upper limit"
self.ulab['style'] = 'upr.TLabel'
def is_okay(self, text, inp, ind):
""" validation function
Parameters
----------
text : str
text if allowed
inp : str
current input
Returns
-------
boolean
"""
if text in ("", "-"):
return True
try:
int(text)
except ValueError:
return False
return True
class FloatEntry(IntegerEntry):
"""Float class for entry
Parameters
----------
parent : str
parent handle
lf_text : str
text on LabelFrame
l_limit : float
lower limit
u_limit : float
upper limit
def_inp : str
default text
colour : str
frame colour
mod : boolean
enable or disable state switch
Returns
-------
float
"""
def __init__(self, parent, lf_text, l_limit, u_limit, def_inp="",
colour='brown', mod=False):
'''
#self.parent = parent
#self.lf_text = lf_text
#self.mod = mod
#self.def_inp=def_inp
#self.colour = colour
#self.l_limit = l_limit
#self.u_limit = u_limit
'''
super().__init__(parent, lf_text, l_limit, u_limit, def_inp, colour, mod)
self.out_var = DoubleVar()
self.out_var.set(def_inp)
self.construct(colour)
self.make_entry()
self.limits()
def is_okay(self, text, inp, ind):
""" validation function
Parameters
----------
text : str
text if allowed
inp : str
current input
Returns
-------
boolean
"""
if text in ("", "-", ".", "-."):
return True
try:
float(text)
except ValueError:
return False
return True
if __name__ == "__main__":
root = Tk()
fra0 = Frame(root)
fra0.grid(row=0, column=0, columnspan=3)
COLOUR = 'green'
MOD = True
LF_TEXT = 'Beer Type'
DEF_INP = 'Pilsner'
v0 = StringEntry(fra0, LF_TEXT, DEF_INP, COLOUR, MOD)
v0.update_idletasks()
LF_TEXT = 'Number of Coils'
DEF_INP = 10
L_LIMIT = 1
U_LIMIT = 100
v1 = IntegerEntry(fra0, LF_TEXT, L_LIMIT, U_LIMIT, DEF_INP, COLOUR, MOD)
v1.update_idletasks()
LF_TEXT = 'Beer Strength v/v % alcohol'
DEF_INP = 5.5
L_LIMIT = 0.5
U_LIMIT = 10.5
v2 = FloatEntry(fra0, LF_TEXT, L_LIMIT, U_LIMIT, DEF_INP, COLOUR, MOD)
v2.update_idletasks()
v2w = v2.winfo_reqwidth()
v2h = v2.winfo_reqheight()
v1w = v1.winfo_reqwidth()
v1h = v1.winfo_reqheight()
v0w = v0.winfo_reqwidth()
v0h = v0.winfo_reqheight()
maxw = max(v0w, v1w, v2w)
maxh = max(v0h, v1h, v2h)
v0.grid(column=0, row=0, ipadx=(maxw - v0w) // 2, ipady=(maxh - v0h) // 2)
v1.grid(column=1, row=0, ipadx=(maxw - v1w) // 2, ipady=(maxh - v1h) // 2)
v2.grid(column=2, row=0, ipadx=(maxw - v2w) // 2, ipady=(maxh - v1h) // 2)
b0 = Button(root, text='Click after string selection',
command=lambda: print(v0.out_var.get(), v0.messlbl['text']))
b0.grid(row=2, column=0)
b1 = Button(root, text='Click after integer selection',
command=lambda: print(v1.out_var.get(), v1.messlbl['text']))
b1.grid(row=2, column=1)
b2 = Button(root, text='Click after float selection',
command=lambda: print(v2.out_var.get(), v2.messlbl['text']))
b2.grid(row=2, column=2)
root.mainloop()