Override class where mutliple kwargs are used in different functions - python

I am looking to override the Tkinter Frame, Button, Lable and Entry widgets to have them creted and packed in the same line of code. (IMO 2 lines to do this and get a reference for the obj is ugly, inefficient and harder to read even if it provides more flexability).
My previous code was this:
def cFrame(self, element, bg="white", borderwidth=0, relief="groove", side=None, padx=0, pady=0, height=0, width=0, expand=0, fill=None, image=None, highlightbackground=None, highlightcolor=None, highlightthickness=0, ipadx=0, ipady=0):
f = self.TkUtil.Frame(element, bg=bg, borderwidth=borderwidth, relief=relief, height=height, width=width, image=image, highlightbackground=highlightbackground, highlightcolor=highlightcolor, highlightthickness=highlightthickness)
f.pack(side=side, padx=padx, pady=pady, ipadx=ipadx, ipady=ipady, expand=expand, fill=fill)
return f
My proposed class would look like this:
class cFrame(Frame):
def __init__(self, master, **kwargs):
Frame.__init__(*(self, master), **kwargs)
self.pack(**kwargs)
The issue with this being **kwargs does not care if the keyword is valid and returns 'bad option -"kwarg"'.
I have tried expanding the function a bit more but I keep having issues:
class cButton(Button):
def __init__(self, master, **kwargs):
Button.__init__(*(self, master))
self.conf(**kwargs)
def conf(self, **kwargs ):
__pk_ops = {}
for k, v in kwargs.items():
try:
self.configure({k:v})
except:
__pk_ops[k] = v
self._pk(__pk_ops)
def _pk(self, __ops):
self.pack(__ops)
In this code, I have to loop through all kwargs before and indervidually configure them which is a lot of wasted time over the 1000's of widgets needed. Additionally, I have issues where self.configure(text="sometext") errors so 'text' is passed through to the self.pack() method because for some magic reason it doesn't like that option in particular (same for compound in this example too).
My end goal is to have x = Frame(root, bg="black") \n x.pack(side=TOP) be replaced with x = cFrame(root, bg="black", side=TOP) without default any of the options like my current code making any changes difficult and any sort of theam almost impossible.

You can create a custom class to override the pack() as below:
def cWidget:
def pack(self, **kw):
super().pack(**kw) # call original pack()
return self # return widget itself
# can do the same for grid() and place()
Then create custom widget class as below:
import tkinter as tk
class cButton(cWidget, tk.Button): pass
# do the same for other widgets you want
Now you can use the custom button class as below:
...
root = tk.Tk()
def clicked():
print('Button clicked')
btn['text'] = 'Clicked' # can use 'btn' to change its text as well
btn = cButton(root, text='Click me!', command=clicked, bg='gold').pack(padx=10, pady=10)
print(f'{btn=}') # here you won't get None
print(isinstance(btn, tk.Button)) # here you get True
...
Note that for Python 3.8+, you can simply use the Walrus Operator (:=):
(btn := tk.Button(root, text='Click me!')).pack(padx=5, pady=5)

Related

Nested Class factory with tkinter

I'm trying to build a script for import in my future projects.
That Script should create some tk.Frames in a tk.Frame and let me edit the created ones in a main.
I think, the best way to get there is to create a Holder_frame class and put some nested classes in.
so I could call them in my main with Holder_frame.F1.
I tried a lot of code and I ended up here making me an account.
Anyway here is where Im at:
import tkinter as tk
from tkinter import Frame,Button
class BaseClass(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.pack()
class Holder_frame(tk.Frame):
Names = []
def __init__(self, master, frames=2):
tk.Frame.__init__(self, master)
self.master = master
frame_names = Holder_frame.Names
for i in range(0,frames):
frame_names.append("F"+str(i+1))
print(frame_names)
Holder_frame.factory()
def factory():
print(Holder_frame.Names)
print(type(BaseClass))
for idex,i in enumerate (Holder_frame.Names):
print(i)
class NestedClass(BaseClass):
pass
NestedClass.__name__ = i
NestedClass.__qualname__ = i
if __name__ == "__main__":
root = tk.Tk()
def raise1():
Holder_frame.F1.tkraise()
def raise2():
Holder_frame.F2.tkraise()
holder=Holder_frame(root,frames=2)
holder.grid(row=1,column=0)
b1 = tk.Button(root, text='1', command=raise1)
b1.grid(row=0,column=0)
b2 = tk.Button(root, text='2', command=raise2)
b2.grid(row=0,column=1)
root.mainloop()
Everything works fine, till I try to call a Frame.
(AttributeError 'Holder_frame' object has no attribute 'F1')
I think my problem is the structure but need some help to solve it.
Any suggestions?
If I'm getting it right I think you mean to have some sort of a Base class that has some configuration which a set of frames have in common like for example you want to have 10 frames of 300x400 geometry and of a brown background in common and later having another set of frames with a different configuration, which can be accessed in an organised way. Then I would say you have an interesting way but I would rather use a list or a dictionary anyway.
Here are some approaches to achieve this goal.
Approach 1
In this approach, I've created a function that returns a dictionary with all the frames created and contained in it like in format ({..., 'F20': tkinter.frame, ...})
import tkinter as tk
def get_base_frames(num, master, cnf={}, **kw):
"""
Create list of frames with common configuration options.
Args:
num (int): Number of frames to be created.
master (tk.Misc): Takes tkinter widget or window as a parent for the frames.
cnf (dict): configuration options for all the frames.
kw: configuration options for all the frames.
Return:
Dictionary of frames ({..., 'F20': tkinter.frame, ...}).
"""
return {f'F{n+1}': tk.Frame(master, cnf=cnf, **kw) for n in range(num)}
if __name__ == "__main__":
root = tk.Tk()
frame_holder = get_base_frames(10, root, width=50, height=50, bg='brown')
# Frames can be accessed through their names like so.
print(frame_holder.get('F1'))
Approach 2
Here I've used class and objects. Where I made this class Frames though you can name it anything you want. I also added some important method like cget() and configure(), through these methods once get a value to an option and configure options for all the frames respectively. There are more useful methods like bind() and bind_all() if you need those just modify this class as per your need.
import tkinter as tk
class Frames(object):
def __init__(self, master=None, cnf={}, **kw):
super().__init__()
num = cnf.pop('num', kw.pop('num', 0))
for n in range(num):
self.__setattr__(f'F{n+1}', tk.Frame(master, cnf=cnf, **kw))
def configure(self, cnf={}, **kw):
"""Configure resources of a widget.
The values for resources are specified as keyword
arguments. To get an overview about
the allowed keyword arguments call the method keys.
"""
for frame in self.__dict__:
frame = self.__getattribute__(frame)
if isinstance(frame, tk.Frame):
if not cnf and not kw:
return frame.configure()
frame.configure(cnf=cnf, **kw)
config = configure
def cget(self, key):
"""Return the resource value for a KEY given as string."""
for frame in self.__dict__:
frame = self.__getattribute__(frame)
if isinstance(frame, tk.Frame):
return frame.cget(key)
__getitem__ = cget
if __name__ == "__main__":
root = tk.Tk()
frame_holder = Frames(root, num=10, width=10,
bd=2, relief='sunken', bg='yellow')
# Frames can be accessed through their naems like so.
print(frame_holder.F4)
print(frame_holder['bg'])
frame_holder.config(bg='blue')
print(frame_holder['bg'])
Approach 3
If you want to have differently configured frames contained in one class, where all those frames have some method in common or some attribute in common.
import tkinter as tk
class BaseFrame(tk.Frame):
def __init__(self, master=None, cnf={}, **kw):
super().__init__(master=master, cnf={}, **kw)
def common_function(self):
"""This function will be common in every
frame created through this class."""
# Do something...
class FrameHolder(object):
def __init__(self, master=None, cnf={}, **kw):
kw = tk._cnfmerge((cnf, kw))
num = kw.pop('num', len(kw))
for n in range(num):
name = f'F{n+1}'
cnf = kw.get(name)
self.__setattr__(name, BaseFrame(master, cnf))
if __name__ == "__main__":
root = tk.Tk()
holder = FrameHolder(root,
F1=dict(width=30, height=40, bg='black'),
F2=dict(width=50, height=10, bg='green'),
F3=dict(width=300, height=350, bg='blue'),
F4=dict(width=100, height=100, bg='yellow'),
)
print(holder.F1)
print(holder.__dict__)
Approach 4
This is the approach that OP is trying to achieve.
import tkinter as tk
class BaseClass(tk.Frame):
def __init__(self, master, cnf={}, **kw):
kw = tk._cnfmerge((cnf, kw))
cnf = [(i, kw.pop(i, None))
for i in ('pack', 'grid', 'place') if i in kw]
tk.Frame.__init__(self, master, **kw)
self.master = master
if cnf:
self.__getattribute__(cnf[-1][0])(cnf=cnf[-1][1])
class Container(tk.Frame):
"""Container class which can contain tkinter widgets.
Geometry (pack, grid, place) configuration of widgets
can also be passed as an argument.
For Example:-
>>> Container(root, widget=tk.Button,
B5=dict(width=30, height=40, bg='black',
fg='white', pack=(), text='Button1'),
B6=dict(width=50, height=10, bg='green', text='Button2',
place=dict(relx=0.5, rely=1, anchor='s')))
"""
BaseClass = BaseClass
def __init__(self, master=None, cnf={}, **kw):
kw = tk._cnfmerge((cnf, kw))
wid = kw.pop('widget', tk.Frame)
for name, cnf in kw.items():
geo = [(i, cnf.pop(i, None))
for i in ('pack', 'grid', 'place') if i in cnf]
setattr(Container, name, wid(master, cnf))
if geo:
manager, cnf2 = geo[-1]
widget = getattr(Container, name)
getattr(widget, manager)(cnf=cnf2)
if __name__ == "__main__":
root = tk.Tk()
Container(root, widget=Container.BaseClass,
F1=dict(width=30, height=40, bg='black', relief='sunken',
pack=dict(ipadx=10, ipady=10, fill='both'), bd=5),
F2=dict(width=50, height=10, bg='green',
pack=dict(ipadx=10, ipady=10, fill='both')),
)
Container(root, widget=tk.Button,
B5=dict(width=30, height=40, bg='black',
fg='white', pack={}, text='Button1'),
B6=dict(width=50, height=10, bg='green', text='Button2',
place=dict(relx=0.5, rely=1, anchor='s')),
)
print(Container.__dict__)
root.mainloop()
A lot can be done and can be modified according to one's needs, these are just some approaches that I think will work very well to automate and keep a set of frames in shape and together.
There can be multiple ways to do this or maybe something better and efficient than these, feel free to give suggestions and share something new.
One solution to this problem, I think, as I don't fully understand your question, but this here was my solution:
import tkinter as tk
from tkinter import Frame,Button
class BaseClass(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.pack()
class Holder_frame(tk.Frame):
def __init__(self, master, frames=2):
tk.Frame.__init__(self, master)
self.master = master
self.frame_names = []
for i in range(frames):
Holder_frame.create_frames("F"+str(i+1), self)
#classmethod
def create_frames(cls, name, master):
setattr(cls, name, tk.Frame(master))
if __name__ == "__main__":
root = tk.Tk()
def raise1():
print(type(Holder_frame.F1))
def raise2():
print(type(Holder_frame.F2))
holder=Holder_frame(root,frames=2)
holder.grid(row=1,column=0)
b1 = tk.Button(root, text='1', command=raise1)
b1.grid(row=0,column=0)
b2 = tk.Button(root, text='2', command=raise2)
b2.grid(row=0,column=1)
print(Holder_frame.__dict__.items())
root.mainloop()
The use of setattr allows one to add variables to the class, just like if you were to type a function into the code. This allows you to access frames from outside the class as somewhat of a "global variable"
I used a file to test if it work outside as an imported module too:
# main.py
from nested_class import Holder_frame
import tkinter as tk
root = tk.Tk()
holder=Holder_frame(root,frames=1000)
holder.grid(row=1,column=0)
print(Holder_frame.__dict__.items())
root.mainloop()
I hope this answers your question,
James
EDIT:
After thinking there is, what I think, to be a cleaner system for what you want. With the code from this post one can see that your my written system could be replaced by a ttk.Notebook, and by removing the top bar by using style.layout('TNotebook.Tab', []), one can see that you would get a frame widget that could have frame widgets inside of it:
import tkinter as tk
import tkinter.ttk as ttk
class multiframe_example:
def __init__(self, master):
self.master = master
style = ttk.Style()
style.layout('TNotebook.Tab', [])
notebook = ttk.Notebook(self.master)
notebook.grid(row=0, column=0)
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(0, weight=1)
tab1 = tk.Frame(self.master, width=500, height=500, background="green")
tab2 = tk.Frame(self.master, width=500, height=500)
tab3 = tk.Frame(self.master, width=500, height=500)
notebook.add(tab1)
notebook.add(tab2)
notebook.add(tab3)
notebook.select(0) # select tab 1
notebook.select(1) # select tab 2
notebook.select(2) # select tab 3
def main():
root = tk.Tk()
root.geometry("500x500")
multiframe_example(root)
root.mainloop()
if __name__ == '__main__':
main()
Hope this code can support you and does as you would like!

Python- Passing 2 sets of **kwargs into same Class. Is it possible?

Lets say I have a class named NewEntry with 2 tkinter widgets, ttk.Entry and ttk.Button, which I want to pass each one of them **kw_ent and **kw_but for Entry and Button widget respectively, to enable a code flexibilty when using this class.
But, as shown in code below, defining 2 kwargs yields an syntax error (which is quite obvious when coding it this way).
Is there a way to pass 2 kwargs?
class NewEntry(ttk.Frame):
def __init__(self, master=None, **kw_ent, **kw_but):
ttk.Frame.__init__(self, master)
self.master = master
self.ent_var = tk.StringVar()
self.ent = ttk.Entry(self, textvariable=self.ent_var, **kw_ent)
self.ent.grid(row=0, column=0)
self.button = ttk.Button(self, text='Button', command=self.but_callback, **kw_but)
self.button.grid(row=0, column=1)
self.ent.bind('<FocusIn>', self.clear_ent)
self.init_vals()
def init_vals(self):
self.ent_var.set("Enter value")
self.ent['foreground'] = 'red'
def clear_ent(self, event):
self.ent_var.set('')
self.ent['foreground'] = '#4c4c4c'
def restore_ent(self, event):
self.init_vals()
def but_callback(self):
print(self.ent_var.get())
sleep(1)
self.init_vals()
root = tk.Tk()
a = NewEntry(root, width=13)
a.grid()
root.mainloop()
Is there a way to pass 2 kwargs?
Yes quite simply: don't use the **kwargs notation.
class NewEntry(ttk.Frame):
def __init__(self, master=None, kw_ent=None, kw_but=None):
if kw_ent is None:
kw_ent = {}
if kw_but is None:
kw_but = {}
# your existsing code here
You will of course have to be a bit more explicit when instanciating NewEntry - assuming that in a = NewEntry(root, width=13), width was supposed to be part of kw_ent you'll need:
a = NewEntry(root, kw_ent={"width":13})
As a side note, you may want to consider a more readable naming scheme than "ent" or "but" - "entry" and "button" come to mind... Short doesn't mean cryptic and 3 letters identifiers won't make your code any faster (but they sure make it less readable).

How to create multiple Label in Button widget of Tkinter?

I would like to know how to create buttons widget in Tkinter with multiple labels as the following figure.
Buttons with sub-label.
As you can see, that in some buttons there is a sub-label, e.g., Button "X" has another small label of "A". I have tried to search for the solution, but found none.
Thank you very much in advance.
You can put your labels in a Frame, and have the Button be the parent of that frame. However, you'd need to be a little bit clever and overcome some issues, such as:
inability to click the button properly (you can only click on edges, because the frame containing labels is in the middle), which means you'd have to do some event-handling (clicking on the frame and the labels inside needs to trigger the same event as if the button was clicked)
unsynchronised colours when hovering over the button itself
and a few other minor details, like properly configuring the button's relief when it's clicked (don't forget, you may be clicking the frame or the labels!), etc.
Here is an MCVE:
import sys
import string
import random
try:
import tkinter as tk
from tkinter import ttk
except ImportError:
import Tkinter as tk
import ttk
CHARS = string.ascii_letters + string.digits
class CustomButton(tk.Button):
"""
CustomButton class inherits from tk.Button, which means it
behaves just like an ordinary tk.Button widget, but it also
has some extended functionality.
"""
def __init__(self, parent, *args, **kwargs):
super().__init__()
self.command = kwargs.get('command')
self.frame = tk.Frame(self)
self.frame.pack(fill='none', expand=False, pady=(3, 0))
self.upper_label = ttk.Label(self.frame, text=kwargs.get('upper_text'))
self.upper_label.grid(row=0, column=0)
self.bottom_label = ttk.Label(self.frame, text=kwargs.get('bottom_text'))
self.bottom_label.grid(row=1, column=1)
self.frame.pack_propagate(False)
self.configure(width=kwargs.get('width'), height=kwargs.get('height'))
self.pack_propagate(False)
self.clicked = tk.BooleanVar()
self.clicked.trace_add('write', self._button_cmd)
self.bind('<Enter>', self._on_enter)
self.bind('<Leave>', self._on_leave)
self.frame.bind('<Enter>', self._on_enter)
self.frame.bind('<Button-1>', self._on_click)
self.upper_label.bind('<Button-1>', self._on_click)
self.bottom_label.bind('<Button-1>', self._on_click)
def _button_cmd(self, *_):
"""
Callback helper method
"""
if self.clicked.get() and self.command is not None:
self.command()
def _on_enter(self, _):
"""
Callback helper method which is triggered
when the cursor enters the widget's 'territory'
"""
for widget in (self, self.frame, self.upper_label, self.bottom_label):
widget.configure(background=self.cget('activebackground'))
def _on_leave(self, _):
"""
Callback helper method which is triggered
when the cursor leaves the widget's 'territory'
"""
for widget in (self, self.frame, self.upper_label, self.bottom_label):
widget.configure(background=self.cget('highlightbackground'))
def _on_click(self, _):
"""
Callback helper method which is triggered
when the the widget is clicked
"""
self.clicked.set(True)
self.configure(relief='sunken')
self.after(100, lambda: [
self.configure(relief='raised'), self.clicked.set(False)
])
class KeyboardMCVE(tk.Tk):
"""
MCVE class for demonstration purposes
"""
def __init__(self):
super().__init__()
self.title('Keyboard')
self._widgets = []
self._create_widgets()
def _create_widgets(self):
"""
Instantiating all the "keys" (buttons) on the fly while both
configuring and laying them out properly at the same time.
"""
for row in range(5):
current_row = []
for column in range(15):
button = CustomButton(
self,
width=1, height=2,
upper_text=random.choice(CHARS),
bottom_text=random.choice(CHARS)
)
button.grid(row=row, column=column, sticky='nswe')
current_row.append(button)
self._widgets.append(current_row)
if __name__ == '__main__':
sys.exit(KeyboardMCVE().mainloop())
Alternatively, a simple workaround would be to use Unicode superscripts/subscripts.

tkinter, "refresh" the mainwindow

Though I think that the solution might be similar to this one: tkinter, display a value from subwindow in mainwindow , I still decided to ask this question since I have trouble to figure it out on my own.
I have the list "fields" with which I am creating any given number of rows, with two labes inside of them. After opening a subwindow, I want to be able to manipulate that list (in my example simply just append at the moment) and after clicking on a button (here "ADD"), I want the mainwindow to update, so that it shows the rows of the manipulated list. It works fine for the most part but I dont what is the best way to update the mainwindow in this example.
The only solution I was able to come up with, is to destroy the mainwindow and recreate it but I have the feeling that this might not be the best solution. Is there a better one?
import tkinter as tk
a=0
fields=[("a",1),("c",2),("e",3)]
class clsApp(object):
def __init__(self):
self.root=tk.Tk()
self.root.title("MainWindow")
##Labels##
self.rootLabel=tk.Label(self.root, text="WindowAppExperiment", padx=100)
self.aLabel=tk.Label(self.root, text=a, padx=100)
##Buttons##
self.BtExit=tk.Button(self.root, text="Quit", fg="red", command=self.root.quit)
###self.BtNewWindow=tk.Button(self.root, text ="Edit", command=lambda:self.clsNewWindow(self.root, self.aLabel).run())
self.BtNewField=tk.Button(self.root, text ="New Field", padx=30, command=lambda:self.clsNewFields(self.root).run())
def grid (self):
self.rootLabel.pack()
self.aLabel.pack()
self.fckPackFields()
self.BtNewField.pack()
self.BtExit.pack(side="left")
###self.BtNewWindow.pack(side="right")
def fckPackFields(self):
if fields:
for field in fields:
##create labels##
row=tk.Frame(self.root)
nameLabel=tk.Label(row, text =field[0], width=20, anchor="w")
valueLabel=tk.Label(row, text =field[1], width=5)
##pack labels##
row.pack(side="top", fill="x", padx=5, pady=5)
nameLabel.pack(side="left")
valueLabel.pack(side="right", expand=True, fill="x")
def run(self):
self.grid()
self.root.mainloop()
self.root.destroy()
class clsNewFields(object):
def __init__(self, Parent):
self.parent=Parent
##Window##
self.top=tk.Toplevel()
self.top.title("Add Fields")
##Labels##
self.enterNameLabel=tk.Label(self.top, text ="Enter fieldname", padx=10)
self.enterValueLabel=tk.Label(self.top, text ="Enter value", padx=10)
##Entryfields##
self.EntryName=tk.Entry(self.top)
self.EntryValue=tk.Entry(self.top)
##Buttons##
self.BtADD=tk.Button(self.top, text ="ADD", command=lambda:self.fckAddField(self.EntryName, self.EntryValue))
self.BtClose=tk.Button(self.top, text ="Close", command=self.top. quit)
def grid(self):
self.enterNameLabel.pack()
self.enterValueLabel.pack()
self.EntryName.pack()
self.EntryValue.pack()
self.BtADD.pack()
self.BtClose.pack()
def fckAddField(self, Name, Value):
self.name=Name.get()
self.value=Value.get()
global fields
fields.append((self.name, self.value))
print(fields)
self.parent.update
def run(self):
self.grid()
self.top.mainloop()
self.top.destroy()
clsApp().run()
Welcome to StackOverflow.
First of all - do you really want to declare the clsNewFields inside your clsApp ? Yes, the Fields should be used inside App, but i do not see a need for using class-in-class-declaration.
Second - you are packing the Fields in def fckPackFields(self):. This is not automatically called when you update it.
You are not calling update function by using self.parent.update.
You are using global variable for fields, what does not really suit your needs. Why not having a list inside your App-class like:
def __init__(self):
self.__fields=[]
def __set_fields(self, value):
self.__fields=value
def __get_fields(self):
return self.__fields
Fields = property(__get_fields, __set_fields)
def __loadUI(self, event=None):
# This function should be called at the end of __init__
self.fieldFrame=tk.Frame(self.root)
self.fieldFrame.pack(side="top")
def fckPackFields(self):
#First clean area
[...]
#Then add fields
for field in self.__fields:
# create the row, etc.
# !!! but do it inside self.fieldFrame !!!
[...]
I would prefer using grid instead of pack over here, because there I think it is easier to place a frame at a certain position, then you could just destroy self.fieldFrame and recreate it at the same position for placing the fields in it.
UPDATE:
Just checked your code again. With some simple tricks your can tweak your GUI to do what you want:
def __init__(self):
self.fieldFrame=None #this line has been added
#completely reworked this function
def grid(self):
self.rootLabel.grid(row=1, column=0, columnspan=2, sticky=tk.NW+tk.SE)
self.fckPackFields()
self.BtNewField.grid(row=3, column=0, sticky=tk.NW+tk.SE)
self.BtExit.grid(row=3, column=1, sticky=tk.NW+tk.SE)
#Only one line changed / one added
def fckPackFields(self):
self.__cleanFields() #added this line, function beyond
if fields:
for field in fields:
##create labels##
row=tk.Frame(self.fieldFrame) #add the row to the fieldFrame
[...]
#In here we destroy and recreate fieldFrame as needed
def __cleanFields(self):
if self.fieldFrame:
self.fieldFrame.destroy()
##FieldFrame##
self.fieldFrame=tk.Frame(self.root)
self.fieldFrame.grid(row=2, column=0, columnspan=2)
In clsNewFields:
def fckAddField(self, Name, Value):
[...]
self.parent.fckPackFields() # instead of self.parent.update
EDIT:
Have a look at these two questions:
Class inside a Class
Benefit of nested Classes
I did not mean to point out that nested classes are to be avoided in general but I do want to focus you into the thought of "is there a real necessity or benefit of it for my use-case".

Setting a tkinter variables using a generic set_value() method

I'd like to have a generic method that will change the value of a tkinter variable. I want to be able to do this from a menu option that brings up a new dialog box which is from a generic method that can be used for different variables. the code I have so far is below:
import sys
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
import sequencer as seq
class View(ttk.Frame):
"""Main Gui class"""
def __init__(self, master = None):
ttk.Frame.__init__(self, master, borderwidth=5, width=450, height=500)
self.master = master
self.grid(column=0, row=0, sticky=(N, S, E, W))
self.columnconfigure(0, weight=1)
###############################
### User editable variables ###
self.precision = IntVar(value=4, name='precision')
self.sensitivity = IntVar(value = 50, name='sensitivity')
### User editable variables ###
###############################
self.create_menus()
def create_menus(self):
"""Produces the menu layout for the main window"""
self.master.option_add('*tearOff', FALSE)
self.menubar = Menu(self.master)
self.master['menu'] = self.menubar
# Menu Variables
menu_file = Menu(self.menubar)
menu_edit = Menu(self.menubar)
# Add the menus to the menubar and assign their variables
self.menubar.add_cascade(menu=menu_file, label="File")
self.menubar.add_cascade(menu=menu_edit, label = "Edit")
### ADD COMMANDS TO THE MENUES ###
### File ###
menu_file.add_command(label="Quit", command=self.master.destroy)
### Edit ###
menu_edit.add_command(label="Backbone", command=lambda : self.edit_backbone())
menu_edit.add_command(label="Precision", command=lambda : self.precision.set(self.set_value_int("Precision")))
menu_edit.add_command(label="Sensitivity", command=lambda : self.sensitivity.set(self.set_value_int("Sensitivity")))
menu_edit.add_command(label="print precision", command=lambda : print(self.precision.get()))
menu_edit.add_command(label="print sensitivity", command=lambda : print(self.sensitivity.get()))
def set_value_int(self, name):
"""Standards dialog that return a user define value of a specific type"""
t = Toplevel(self)
t.title("Set " + name)
label = ttk.Label(t, text="Set "+name)
label.grid(row=0)
entry = ttk.Entry(t)
entry.grid(row=1)
cancel = ttk.Button(t, text="Cancel", command=lambda : t.destroy())
cancel.grid(column=0, row=2)
okey = ttk.Button(t, text="Okey", command=lambda : okey(entry.get()))
okey.grid(column=1, row=2)
def okey(value):
"""return value according to type"""
try:
t.destroy()
return int(value)
except:
self.error_box("value must be and integer")
def error_box(self, error_message="Unknown error"):
"""(Frame, String) -> None
Opens an window with an Okey button and a custom error message"""
t=Toplevel(self)
t.title("Error")
label = ttk.Label(t, text=error_message)
label.grid(row=0)
okey = ttk.Button(t, text="Okey", command=lambda : t.destroy())
okey.grid(row=1)
if __name__ == "__main__":
root = Tk()
root.title("Sequencer")
view = View(root)
root.mainloop()
print("End")
The Edit-> print xxxxxx commands are purely for testing purposes to check if the values have changed. If these are executed before trying to change the values of precision or sensitivity then they work as expected.
If you try to change either of the tkinter variables in the way I have tried to do they become None types and I can't see why. I can only assume that you are not allowed to change them in the way that I have but I can't think of another way to do it without having a separated method for each variable which I'd like to avoid.
Baicly I'd like the user to be able to customise the variables precision and sensitivity and use the same method in the code to change the values.
Extra but not necessarily vital:- If there is a way to define which type the variable should be in the methods arguments as well that would be even better as I will have other variables for the user to change later and they will be of different types.
Any help would be much appreciated. I have tried to be as clear as I can but let me know if anything is not.
Thanks
self.set_value_int always returns None, so it's always going to set your variable to none.
Instead of trying to write a complex lambda that is hard to debug, put all of your logic inside the function. Have the function set the value. All you need to do is tell it what variable to set:
menu_edit.add_command(label="Precision",
command=lambda name="Precision", var=self.precision: self.set_value_int(name, var))
...
def set_value_int(self, name, var):
...
def okey():
s = entry.get()
try:
var.set(int(s))
...

Categories

Resources