I'm struggling to get my method working correctly. I've thought about using a lambda function which I did for another problem and that worked, however here it does not seem to work. I'm trying to change the functions to methods and for some reason, my method is not working correctly since it has no reference to the tree. I've tried using a lambda function although that does not work.
My error:
NameError: name 'tree' is not defined
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "/home/bitvivaz/Documents/Software Development/Python/PasswordManager/mainFrame.py", line 54, in select
print([tree.item(x) for x in tree.selection()])
NameError: name 'tree' is not defined
Here is my code:
import tkinter as tk
import tkinter.ttk as ttk
from encryption import encrypted_password, decrypt_password
import backend as db
def get_credentials(tree):
'''Retrieves all credentials from the database and inserts it into the tree widget'''
for row in db.show_credentials():
tree.insert("", 'end', text=row['name'], values=(
row['username'], decrypt_password(row['password'])))
class MainframeApp:
def __init__(self, master=None):
# build ui
frame_main = ttk.Frame(master)
frame_main.config(height='600', width='600')
frame_main.grid()
# Creates tree widget
tree = ttk.Treeview(frame_main)
tree["columns"] = ("one", "two")
tree.column("#0")
tree.column("one")
tree.column("two")
tree.heading("#0", text="Website")
tree.heading("one", text="Username")
tree.heading("two", text="Password")
tree.grid(padx='5', pady='5', rowspan='20')
get_credentials(tree)
tree.bind("<<TreeviewSelect>>", self.select, "+")
button_add = ttk.Button(frame_main)
button_add.config(text='Add')
button_add.grid(column='1', padx='5', row='0')
button_delete = ttk.Button(frame_main)
button_delete.config(text='Delete')
button_delete.grid(column='1', padx='5', row='1')
button_logout = ttk.Button(frame_main)
button_logout.config(text='Logout')
button_logout.grid(column='1', padx='5', row='2')
# Main widget
self.mainwindow = frame_main
def select(self, e):
print([tree.item(x) for x in tree.selection()])
def run(self):
self.mainwindow.mainloop()
if __name__ == '__main__':
root = tk.Tk()
root.title("Password Manager")
app = MainframeApp(root)
app.run()
When you make use of a class structure, certain variables are available across methods (class or instance variables), others are not (local variables).
In your case, you need to define variables you need across methods as instance variables, that is, rather than:
tree = ttk.Treeview(frame_main)
You declare:
self.tree = ttk.Treeview(frame_main)
Then you can reference the variable across methods as self.tree.
Related
I am (for some elaborate setup reasons) trying to retrieve the actual command callback function from tkinter widgets, for example setting up a callback for a button b
import tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='btn', command=lambda:print('foo'))
both
b['command']
b.cget('command')
which I think both are equivalent to
b.tk.call(b._w, 'cget', '-command')
will only return a string like "2277504761920<lambda\>" and not the actual command function. Is there a way to get the actual callback function?
I cannot imagine any case and Im not sure at all if this answers your question but it maybe equivalent for what you are looking for:
The invoke method of the button seems pretty equivalent to me. So solution-1 would be:
import tkinter as tk
def hi():
print('hello')
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = b.invoke
#cmd = lambda :b._do('invoke')
root.mainloop()
If this isnt what you looking for you could call the function in tcl level. Solution-2:
import tkinter as tk
def hi():
print('hello')
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = lambda :root.tk.call(b['command'])
#cmd= lambda :root.tk.eval(b['command'])
cmd()
root.mainloop()
Solution 3, would be to return your function by invoke:
import tkinter as tk
def hi():
print('hello')
return hi
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = b.invoke()
print(cmd) #still a string but comparable
root.mainloop()
This is a more complex solution. It patches Misc._register, Misc.deletecommand and Misc.destroy to delete values from dict tkinterfuncs. In this example there are many print to check that values are added and removed from the dict.
import tkinter as tk
tk.tkinterfuncs = {} # name: func
def registertkinterfunc(name, func):
"""Register name in tkinterfuncs."""
# print('registered', name, func)
tk.tkinterfuncs[name] = func
return name
def deletetkinterfunc(name):
"""Delete a registered func from tkinterfuncs."""
# some funcs ('tkerror', 'exit') are registered outside Misc._register
if name in tk.tkinterfuncs:
del tk.tkinterfuncs[name]
# print('delete', name, 'tkinterfuncs len:', len(tkinterfuncs))
def _register(self, func, subst=None, needcleanup=1):
"""Return a newly created Tcl function. If this
function is called, the Python function FUNC will
be executed. An optional function SUBST can
be given which will be executed before FUNC."""
name = original_register(self, func, subst, needcleanup)
return registertkinterfunc(name, func)
def deletecommand(self, name):
"""Internal function.
Delete the Tcl command provided in NAME."""
original_deletecommand(self, name)
deletetkinterfunc(name)
def destroy(self):
"""
Delete all Tcl commands created for
this widget in the Tcl interpreter.
"""
if self._tclCommands is not None:
for name in self._tclCommands:
# print('- Tkinter: deleted command', name)
self.tk.deletecommand(name)
deletetkinterfunc(name)
self._tclCommands = None
def getcommand(self, name):
"""
Gets the command from the name.
"""
return tk.tkinterfuncs[name]
original_register = tk.Misc.register
tk.Misc._register = tk.Misc.register = _register
original_deletecommand = tk.Misc.deletecommand
tk.Misc.deletecommand = deletecommand
tk.Misc.destroy = destroy
tk.Misc.getcommand = getcommand
if __name__ == '__main__':
def f():
root.after(500, f)
root = tk.Tk()
root.after(500, f)
but1 = tk.Button(root, text='button1', command=f)
but1.pack()
but2 = tk.Button(root, text='button2', command=f)
but2.pack()
but3 = tk.Button(root, text='button3', command=lambda: print(3))
but3.pack()
print(root.getcommand(but1['command']))
print(root.getcommand(but2['command']))
print(root.getcommand(but3['command']))
but3['command'] = f
print(root.getcommand(but3['command']))
root.mainloop()
Looking at tkinter.__init__.py:
class BaseWidget:
...
def _register(self, func, subst=None, needcleanup=1):
"""Return a newly created Tcl function. If this
function is called, the Python function FUNC will
be executed. An optional function SUBST can
be given which will be executed before FUNC."""
f = CallWrapper(func, subst, self).__call__
name = repr(id(f))
try:
func = func.__func__
except AttributeError:
pass
try:
name = name + func.__name__
except AttributeError:
pass
self.tk.createcommand(name, f)
if needcleanup:
if self._tclCommands is None:
self._tclCommands = []
self._tclCommands.append(name)
return name
and
class CallWrapper:
"""Internal class. Stores function to call when some user
defined Tcl function is called e.g. after an event occurred."""
def __init__(self, func, subst, widget):
"""Store FUNC, SUBST and WIDGET as members."""
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit:
raise
except:
self.widget._report_exception()
We get that tkinter wraps the function in the CallWrapper class. That means that if we get all of the CallWrapper objects we can recover the function. Using #hussic's suggestion of monkey patching the CallWrapper class with a class that is easier to work with, we can easily get all of the CallWrapper objects.
This is my solution implemented with #hussic's suggestion:
import tkinter as tk
tk.call_wappers = [] # A list of all of the `MyCallWrapper` objects
class MyCallWrapper:
__slots__ = ("func", "subst", "__call__")
def __init__(self, func, subst, widget):
# We aren't going to use `widget` because that can take space
# and we have a memory leak problem
self.func = func
self.subst = subst
# These are the 2 lines I added:
# First one appends this object to the list defined up there
# the second one uses lambda because python can be tricky if you
# use `id(<object>.<function>)`.
tk.call_wappers.append(self)
self.__call__ = lambda *args: self.call(*args)
def call(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit:
raise
except:
if tk._default_root is None:
raise
else:
tk._default_root._report_exception()
tk.CallWrapper = MyCallWrapper # Monkey patch tkinter
# If we are going to monkey patch `tk.CallWrapper` why not also `tk.getcommand`?
def getcommand(name):
for call_wapper in tk.call_wappers:
candidate_name = repr(id(call_wapper.__call__))
if name.startswith(candidate_name):
return call_wapper.func
return None
tk.getcommand = getcommand
# This is the testing code:
def myfunction():
print("Hi")
root = tk.Tk()
button = tk.Button(root, text="Click me", command=myfunction)
button.pack()
commandname = button.cget("command")
# This is how we are going to get the function into our variable:
myfunction_from_button = tk.getcommand(commandname)
print(myfunction_from_button)
root.mainloop()
As #hussic said in the comments there is a problem that the list (tk.call_wappers) is only being appended to. THe problem will be apparent if you have a .after tkinter loop as each time .after is called an object will be added to the list. To fix this you might want to manually clear the list using tk.call_wappers.clear(). I changed it to use the __slots__ feature to make sure that it doesn't take a lot of space but that doesn't solve the problem.
When you assign a command to a widget, or bind a function to an event, the python function is wrapped in a tkinter.CallWrapper object. That wrapper contains a reference to the python function along with a reference to the widget. To get a callback for a widget you can iterate over the instances of the wrapper in order to get back the original function.
For example, something like this might work:
import tkinter as tk
import gc
def get_callback(widget):
for obj in gc.get_objects():
if isinstance(obj, tk.CallWrapper) and obj.widget == widget:
return obj.func
return None
You can then directly call the return value of this function. Consider the following block of code:
import tkinter as tk
import gc
def get_callback(widget):
for obj in gc.get_objects():
if isinstance(obj, tk.CallWrapper) and obj.widget == widget:
return obj.func
def do_something():
print(f"button1: {get_callback(button1)} type: {type(get_callback(button1))}")
print(f"button2: {get_callback(button2)} type: {type(get_callback(button2))}")
root = tk.Tk()
button1 = tk.Button(root, text="do_something", command=do_something)
button2 = tk.Button(root, text="lambda", command=lambda: do_something())
button1.pack(padx=20, pady=20)
button2.pack(padx=20, pady=20)
root.mainloop()
When I click either button, I see this in the console output which proves that the get_callback method returns a callable.
button1: <function do_something at 0x103386040> type: <class 'function'>
button2: <function <lambda> at 0x103419700> type: <class 'function'>
Button is a object you can assign attributes just define your function outside the button and assign the function ass a attribute
func_print = lambda: print("nice")
x = Button(..., command=func_print)
x.my_func = func_print
def something():
x.my_func()
something()
>>> nice
I was looking same problem but I could not find any nice answer then I created mine actually it is very easy
I am working on a data acquisition project, building multiple data-monitoring/controlling programs for different instruments (voltmeter, camera, etc.) using python. I am using python3 and tkinter (due to its open license) as my GUI.
The basic structure for each instrument right now is:
import packages
class all_GUI():
def __init__():
device = volt_device()
functions linking GUI elements to HW calls
mainloop()
class volt_device():
def __init__():
functions to access HW functionality
mainapp = all_GUI()
It more-less works, but there are many calls between GUI and hardware classes all over the code. If I want to reuse GUI part of the code and link it with another hardware board I pretty much have to rewrite the whole thing. As you can imagine this is not very appealing :-)
I suppose class volt_device can be moved into a separate file and loaded as needed. But because GUI calls many functions from HW part, each HW file (supporting different board, for example) would have to have the exact same naming convention. Not terrible, but not the best either I think.
I was looking into separating GUI and HW as much as possible, but had some difficulties. I was looking into a model-view-controller pattern, but could not make it work. My idea was having three programs:
import GUI
import HW
objGUI =
objHW =
link functions to interface objects
mainloop()
class GUI():
def __init__():
build GUI here with all elements
(this is getting sticky since I need to define functions to be executed when GUI values change
or buttons are pushed)
Have multiple hardware files supporting different instruments.
class HW():
def __init__():
define hardware board, have functions to change/monitor values
Ideally, I would have a relatively simple HW file (file 3). To have whole new virtual device I would have to load GUI portion (file 2; unmodified) and write a simple "controller" (file 1) linking GUI elements to HW functions. Sounds simple ...
I got stuck when I tried to link GUI and HW together. I was not sure how to properly address GUI elements and assign them appropriate HW call/function. Perhaps the whole idea is flawed and the GUI/HW separation needs to approached differently ...
I am sure this problem must have been tackled before I just cannot find it ... or figure it out right now. I would greatly appreciate any suggestions and/or coding references you might have.
Thank you.
Radovan
...would have to have the exact same naming convention. Not terrible, but
not the best either I think.
On the contrary, that is probably the best method. In essence you would create a generic interface and have each "board" implement the interface with it's specifics or subclass something that does. Then you create a class for tkinter that can build an interface from the methods and arguments.
Both displays were automatically generated and one way or another everything leads back to the most basic component.
very generic and simplified example:
import tkinter as tk, abc
from typing import List, Tuple, Callable, Iterable, Dict
import inspect
#create formal interface
class IGenericBoard(metaclass=abc.ABCMeta):
#classmethod
def __subclasshook__(cls, subclass):
isinterface = hasattr(subclass, 'read_pin') and callable(subclass.read_pin)
isinterface &= hasattr(subclass, 'write_pin') and callable(subclass.write_pin)
return isinterface
#abc.abstractmethod
def generic_pin_read(self, pin:int) -> int:
raise NotImplementedError
#abc.abstractmethod
def generic_pin_write(self, pin:int, data:int):
raise NotImplementedError
#implement IGenericBoard
class GenericBoard(IGenericBoard):
#property
def model(self):
#the "model type" for this board instance
return type(self).__name__
#property
def prefix(self) -> List:
#the prefix(es) to use when finding functions
return self._prefix if isinstance(self._prefix , (List, Tuple)) else [self._prefix]
#property
def msgvar(self) -> tk.StringVar:
#the output message var
return self._msgvar
#property
def attributes(self) -> Dict:
#get everything in one shot ~ for **kwargs
return dict(
model =self.model ,
prefix=self.prefix,
msgvar=self.msgvar,
)
def __init__(self):
self._prefix = 'generic'
self._msgvar = tk.StringVar()
def generic_pin_read(self, pin:int) -> int:
self._msgvar.set(f'reading pin {pin}')
#... really do this
return 0
def generic_pin_write(self, pin:int, data:int):
self._msgvar.set(f'writing {data} on pin {pin}')
#... really do this
#"final" class
class LEDBoard(GenericBoard):
def __init__(self):
GenericBoard.__init__(self)
self._prefix = self.prefix + ['led']
def led_blink_write(self, pin:int=13):
self.generic_pin_write(pin, 1)
self._msgvar.set(f'blinking on pin {pin}')
#... really do this
''' tkBaseBoard
the baseclass for all "tk[Version]Board" classes
generates form interfaces for methods with the proper prefix(es)
'''
class tkBaseBoard(tk.Frame):
def __init__(self, master, model, msgvar, prefix, **kwargs):
tk.Frame.__init__(self, master, **{'bd':2, 'relief':'raised', **kwargs})
self.grid_columnconfigure(0, weight=1)
#board model label
tk.Label(self, text=model, font="Consolas 12 bold").grid(row=0, column=0, sticky='w')
#message output from board
self.output_ent = tk.Entry(self, width=30, textvariable=msgvar)
self.output_ent.grid(row=2, column=0, sticky='e')
#common feature label configuration
self.lbl_opts = dict(width=6, anchor='w', font='Consolas 10')
#annotation conversion
self.conversion = {
"<class 'int'>" :lambda: tk.IntVar(),
"<class 'str'>" :lambda: tk.StringVar(),
"<class 'bool'>" :lambda: tk.BooleanVar(),
"<class 'float'>":lambda: tk.DoubleVar(),
}
#build a feature for every "feat_" suffixed method
for feature in [func for func in dir(self) if callable(getattr(self, func)) and func.split('_')[0] in prefix]:
self._add_feature(feature)
#create a list of variable values
def __tovalue(self, vars) -> List[int]:
return [v.get() for v in vars]
#dynamically create the gui for a method
def _add_feature(self, feature):
main = tk.Frame(self)
main.grid(sticky='we')
#parse feature components
command = getattr(self, feature)
featcmp = feature.split('_')
if featcmp and len(featcmp) == 3:
_, label, action = featcmp
#create a list of Vars based on command argument types
args, vars = inspect.signature(command).parameters, []
for name in args:
try:
#convert annotations to the proper tk.[Type]Var
vars.append(self.conversion[str(args[name].annotation)]())
except KeyError:
#fallback to StringVar
vars.append(tk.StringVar())
#create label and button for this command
tk.Label(main, text=label, **self.lbl_opts).grid(row=0, column=0, sticky='e')
tk.Button(main, text=action, width=5, command=lambda v=vars: command(*self.__tovalue(v))).grid(row=0, column=1, sticky='w', padx=8)
#create an Entry for every argument in command
for i, v in enumerate(vars):
tk.Entry(main, width=2, textvariable=v).grid(row=0, column=i+2, sticky='w')
#give all the weight to the last row
main.grid_columnconfigure(i+2, weight=1)
else:
#feature name components did not pass expectations
raise ValueError('ValueError: feature component must consist of three underscore-seperated parts as: PREFIX_LABEL_ACTION')
##EXAMPLES OF THE ULTIMATE IMPLEMENTATION ALL OF THE ABOVE ALLOWS
#generate GenericBoard display
class tkGenericBoard(tkBaseBoard, GenericBoard):
def __init__(self, master, **kwargs):
GenericBoard.__init__(self)
tkBaseBoard.__init__(self, master, **self.attributes, **kwargs)
#generate LEDBoard display
class tkLEDBoard(tkBaseBoard, LEDBoard):
def __init__(self, master, **kwargs):
LEDBoard.__init__(self)
tkBaseBoard.__init__(self, master, **self.attributes, **kwargs)
##EXAMPLE BASE USAGE
if __name__ == '__main__':
root = tk.Tk()
root.title('Example')
root.configure(padx=2, pady=2)
tkGenericBoard(root).grid()
tkLEDBoard(root).grid()
root.mainloop()
I've been trying to raise my second frame from first frame class after the first one is been raised from the root class, but everytime gets an error
type object 'root' has no attribute 'tk'.
But I am able to raise my second frame from the root class but that's not what i want. I would want to raise each consecutive frame from previous frame as i want 4 frames to be raised throughout the program many times with conditions. If I'm thinking all this the wrong way then please provide a workaround, below is my code.
import mysql.connector as sql
from os import getenv
from datetime import date
from ttkthemes import themed_tk as ttkt
from tkinter import ttk
import tkinter as tk
from tkinter import messagebox as mg
class root(ttkt.ThemedTk):
def __init__(self, *args):
ttkt.ThemedTk.__init__(self, *args)
self.geometry('800x450')
self.resizable(0, 0)
self.set_theme('arc') #equilux
self.config(background = '#EFEFEF')
self.navbar()
self.show_frame(home_first)
#### I'm able to call Class_second frame from changing the value here
def show_frame(self, cont):
frame = cont(self)
frame.pack(side = 'right', expand = True, fill= 'both', ipadx = 210)
frame.tkraise()
def show_Class_frame(self):
self.show_frame(self, Class_second)
def about(self):
....
def session(self):
....
#NAVBAR______________________________
def navbar(self):
fl = tk.Frame(self, background = '#0077CC')
fl.pack(side = 'left', expand = 1, fill= 'both')
#NAVBAR's internal items______________________________
....
class home_first(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
....
btnt = ttk.Button(btnfrm, text = 'Teachers', style = 'fntb.TButton', command = self.tch_sel)
# I want to use these to get to second frame
btnt.pack(pady = (0,8))
btns = ttk.Button(btnfrm, text = 'Students', style = 'fntb.TButton', command = self.std_sel)
btns.pack()
def tch_sel(self):
sel = 0 #for teachers direct to page 3 not Class_second Frame
root.show_Class_frame(root)
def std_sel(self):
sel = 1 #for students move to Class_second frame
root.show_Class_frame(root)
class Class_second(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
class vote_third(tk.Frame):
pass #from here i need to bring 4th frame
class done_fourth(tk.Frame):
pass #here there will be like 3 button each going either to Home, Class or Vote frames
if __name__ == '__main__':
app = root()
app.mainloop()
Traceback
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Sagar\Anaconda3\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "d:\Code\Python\py_project_votm\vm_vot_main_oop.py", line 76, in tch_sel
root.show_Class_frame(root)
File "d:\Code\Python\py_project_votm\vm_vot_main_oop.py", line 31, in show_Class_frame
self.show_frame(self, Class_second)
File "d:\Code\Python\py_project_votm\vm_vot_main_oop.py", line 26, in show_frame
frame = cont(self)
File "d:\Code\Python\py_project_votm\vm_vot_main_oop.py", line 84, in __init__
tk.Frame.__init__(self, parent)
File "C:\Users\Sagar\Anaconda3\lib\tkinter\__init__.py", line 2744, in __init__
Widget.__init__(self, master, 'frame', cnf, {}, extra)
File "C:\Users\Sagar\Anaconda3\lib\tkinter\__init__.py", line 2292, in __init__
BaseWidget._setup(self, master, cnf)
File "C:\Users\Sagar\Anaconda3\lib\tkinter\__init__.py", line 2262, in _setup
self.tk = master.tk
AttributeError: type object 'root' has no attribute 'tk'
Also, can someone tell me what other ways would be better to do this program.
Sorry for bad english.
Part of the problem is that you're not using PEP8 naming conventions, which makes your code hard to read and understand. When we read the code we make assumptions based on the naming conventions, and by not using the naming conventions we end up making bad assumptions.
Specifically, all of your classes need to begin with a capital letter (Root, Vote_third, Done_fourth). When we read your code and see something like root.some_method() we're assuming you're calling some_method() on an instance. In your case, however, you're calling it on the class itself.
The problem is this line:
root.show_Class_frame(root)
root is a class, not an instance of a class. This parameter ultimately becomes the master of another widget, but you can't use a class as a master. You must use an instance of a widget class as master.
You are already creating an instance of root, named app. Instead of calling root.show_Class_frame() you need to be calling app.show_Class_frame.
def tch_sel(self):
sel = 0 #for teachers direct to page 3 not Class_second Frame
app.show_Class_frame()
def std_sel(self):
sel = 1 #for students move to Class_second frame
app.show_Class_frame()
Also, as mentioned in the comments, self.show_frame(self, Class_second) needs to be changed to self.show_frame(Class_second).
Eventually I want to use the values in the comboboxes as parameters in other functions, but I think if I can just get them to print for now, that will be enough to build off of. Here's what I have so far.
import tkinter as tk
from tkinter import ttk
import time
def ok():
betType = betTypeVar.get()
season = seasonVar.get()
print(betType, season)
def CreateSimPreviousSeasonWindow():
prevSeasonWindow = tk.Tk()
#============= Bet Type Input =============#
betTypeVar = tk.StringVar()
betTypeLabel = tk.Label(prevSeasonWindow, text="Bet type:").grid(row=0,column=0)
betTypeChosen = ttk.Combobox(prevSeasonWindow, values=['Moneyline','Total'])
betTypeChosen.grid(row=0, column=1)
seasonVar = tk.StringVar()
seasonLabel = tk.Label(prevSeasonWindow, text='Season:').grid(row=1, column=0)
seasonChosen = ttk.Combobox(prevSeasonWindow, values=['2018', '2017'])
seasonChosen.grid(row=1,column=1)
button = tk.Button(prevSeasonWindow, text='OK', command=ok)
button.grid(row=2,column=0)
prevSeasonWindow.mainloop()
This gives me
File "C:[directory...]", line 6, in ok
betType = betTypeVar.get()
NameError: name 'betTypeVar' is not defined
To me it looks pretty obvious that this error is because ok() doesn't have any parameters passed to it, so it has no idea what 'betTypeVar' is, but all the tutorials I've read do it this way, so I'm missing something. If I try actually passing ok() the arguments, it still doesn't work.
There are two things to fix in your code. First let's focus on CreateSimPreviousSeasonWindow:
betTypeVar = tk.StringVar()
seasonVar = tk.StringVar()
You defined two StringVar but you actually never used it or linked them to your combobox object. The correct way is to set them as a textvaraible:
betTypeChosen = ttk.Combobox(prevSeasonWindow, textvariable=betTypeVar, values=['Moneyline','Total'])
seasonChosen = ttk.Combobox(prevSeasonWindow, textvariable=seasonVar, values=['2018', '2017'])
Next, NameError: name 'betTypeVar' is not defined is due to your variables being local variables. You are trying to access the same variable across different functions. To pass them around, you need to declare global:
def ok():
global betTypeVar, seasonVar
betType = betTypeVar.get()
season = seasonVar.get()
print(betType, season)
def CreateSimPreviousSeasonWindow():
global betTypeVar, seasonVar
...
Also I want to point out that if you just want to retrieve the values of the combobox, you don't really need to create two StringVar. Just combobox.get() already works good enough.
import tkinter as tk
from tkinter import ttk
import time
def ok():
global betTypeChosen, seasonChosen
print (betTypeChosen.get(), seasonChosen.get())
def CreateSimPreviousSeasonWindow():
global betTypeChosen,seasonChosen
prevSeasonWindow = tk.Tk()
#============= Bet Type Input =============#
betTypeLabel = tk.Label(prevSeasonWindow, text="Bet type:").grid(row=0,column=0)
betTypeChosen = ttk.Combobox(prevSeasonWindow,values=['Moneyline','Total'])
betTypeChosen.grid(row=0, column=1)
seasonLabel = tk.Label(prevSeasonWindow, text='Season:').grid(row=1, column=0)
seasonChosen = ttk.Combobox(prevSeasonWindow, values=['2018', '2017'])
seasonChosen.grid(row=1,column=1)
button = tk.Button(prevSeasonWindow, text='OK', command=ok)
button.grid(row=2,column=0)
prevSeasonWindow.mainloop()
CreateSimPreviousSeasonWindow()
It says that base_obj is not defined. But I did define it already. So why am I getting this error?
here is the code:
from tkinter import *
root = Tk()
class BaseClass:
def __init__(self,an_int,a_string):
self.the_int = an_int
self.the_string = a_string
class BiggerClass:
def __init__(self,an_instance_of_BaseClass,big_class_string,big_class_int,new_name):
self.the_instance_of_BaseClass = an_instance_of_BaseClass #here we are aggregating the base class into the bigger class
self.the_big_class_string = big_class_string
self.the_big_class_int = big_class_int
self.the_big_class_new_name = new_name
base_int_var = IntVar()
base_string_var = StringVar()
bigger_name_var = StringVar()
entry_base_int = Entry(root,textvariable = base_int_var).pack()
entry_base_string = Entry(root,textvariable = base_string_var).pack()
big_new_name_var = StringVar()
entry_bigger_name = Entry(root, textvariable = bigger_name_var).pack()
entry_big_new_name = Entry(root,textvariable = big_new_name_var).pack()
def create_base_class_instance():
global base_obj
base_obj = BaseClass(base_int_var.get(),base_string_var.get()) # I define 'base_obj' here
list_of_bigs = []
def create_bigger_class_instance(big_handle):
bigger_name_var = big_handle
big_handle = BiggerClass(base_obj,bigger_name_var.get(),55,big_new_name_var.get())
list_of_bigs.append(big_handle)
#global big_obj
#big_obj = BiggerClass(base_obj,bigger_name_var.get(),45)
create_base_class_button = Button(root, text ="create base class", command = create_base_class_instance).pack()
create_big_class_button = Button(root, text ="create big class", command = create_bigger_class_instance(big_new_name_var)).pack()
match_name_var = StringVar()
entry_match_name = Entry(root,textvariable = match_name_var).pack()
def my_button_method():
for a_big in list_of_bigs:
if a_big.the_big_class_new_name == match_name_var:
print(a_big.the_instance_of_BaseClass.the_string)
#print(big_obj.the_instance_of_BaseClass.the_int)
#bigger_class_obj = BiggerClass(base_obj,"hello this is the big class",45)
button_print_out = Button(root,text = "press me", command = my_button_method).pack()
root.mainloop()
here is the error message:
Traceback (most recent call last):
File "C:/Users/TOTTY/PycharmProjects/my game/aggregation practice fork 1.py", line 45, in <module>
create_big_class_button = Button(root, text ="create big class", command = create_bigger_class_instance(big_new_name_var)).pack()
File "C:/Users/TOTTY/PycharmProjects/my game/aggregation practice fork 1.py", line 39, in create_bigger_class_instance
big_handle = BiggerClass(base_obj,bigger_name_var.get(),55,big_new_name_var.get())
NameError: name 'base_obj' is not defined
You have defined the object in create_base_class_instance function and you are calling it in my_button_method.
You should initialize it outside, and use global keyword in both functions.
However using global variables considered code smell. I would advise finding another solution, for example passing base_obj as an argument to both functions.
base_obj = None
def some_function():
global base_obj
# some code referencing base_obj
def other_function():
global base_obj
# some code referencing base_obj
Functions in Python are executed only when they are called. The keyword global is used to indicate that the variable used here is the same as in the global scope. Thus you will need to add a declaring statement in the main class and not in any of the sub function.
For e.g. You will have to write
base_obj = None
In the main class before either of the two functions is called. You do not need global base_obj in your second function as you are not assigning any value to it.
Look at this line of code:
create_big_class_button = Button(..., command = create_bigger_class_instance(big_new_name_var)).pack()
You are immediately calling create_bigger_class_instance(...), and the result of that is getting assigned to the command. Since create_bigger_class_instance relies on the existence of base_obj, and you haven't created base_obj yet since it's tied to a button click, you get the error.
(As a side note, doing something like create_big_class_button = Button(...).pack() will always result in create_big_class_button being set to None, because that is what pack() returns.)