Passing variables between tkinter frame classes in Python - python

I have two frame classes and I want them to have common variable, which changes its value while program is running. I have seen some similar threads, but I do not understand how should I do it in my case. My program consists of three files: one stores gui code and runs program (gui.py), second stores first frame class(frame_1.py), and third stores second frame class(frame_2.py). Gui consists of these two frames and button on the bottom. Frame1 on initial has two empty entry fields, both in one row. There is a button in frame_2 which adds another row of entry fields into Frame1.
gui.py
import customtkinter
from frame_1 import Frame1
from frame_2 import Frame2
class YfinanceGUI:
def __init__(self, app):
self.app = app
self.frame_1 = Frame1(self.app)
self.frame_1.pack(padx=10, pady=10)
self.frame_2 = Frame2(self.app, self.frame_1)
self.frame_2.pack(padx=10, pady=10)
self.print_button = customtkinter.CTkButton(master=self, command=self.print_row)
self.print_button.pack(padx=10, pady=10)
def print_row(self):
print(f"Current row: {roww}")
app = customtkinter.CTk()
running = YfinanceGUI(app)
app.mainloop()
frame_1.py
import customtkinter
class Frame1(customtkinter.CTkFrame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.entry_1 = customtkinter.CTkEntry(self)
self.entry_1.grid(row=1 + roww, column=0, padx=10, pady=10)
self.entry_2 = customtkinter.CTkEntry(self)
self.entry_2.grid(row=1 + roww, column=1, padx=10, pady=10)
frame_2
import customtkinter
class Frame2(customtkinter.CTkFrame):
def __init__(self, frame_1, *args, **kwargs):
super().__init__(*args, **kwargs)
self.button = customtkinter.CTkButton(master=self, command=self.add_row)
self.button.pack(padx=10, pady=10)
self.frame_1 = frame_1
def add_row(self):
self.entry_1 = customtkinter.CTkEntry(self.frame_1)
self.entry_1.grid(row=1 + roww, column=0, padx=10, pady=10)
self.entry_2 = customtkinter.CTkEntry(self.frame_1)
self.entry_2.grid(row=1 + roww, column=1, padx=10, pady=10)
As you can see there is roww variable in all three files, but I do no know where to define it and how to do it.

You could use dataclasses:
data.py:
from dataclasses import dataclass
#dataclass
class C:
a: int # 'a' has no default value
b: int = 5 # assign a default value for 'b'
testdata.py:
import data
print(data.C.b) #Prints 5
data.C.b = 3 #Change value in other file
print(data.C.b) #Prints new value of 3
You can rename the variables, filenames and classname.

If you define a class which contains the data you require and then pass the instance to all classes that needs them, then it will work. This will essentially create a reference to the data instead of just passing the value of the data. Below is an example:
frame_data.py
class FrameData:
def __init__(self) -> None:
self.roww = 0
Usage
from frame_data import FrameData
class UserOfFrameData:
def __init__(self, frame_data: FrameData) -> None:
self.frame_data = frame_data
def increment(self):
self.frame_data.roww += 1
print(self.frame_data.roww)
data = FrameData()
first = UserOfFrameData(data)
second = UserOfFrameData(data)
first.increment()
second.increment()
The code prints 1 then 2, which means they share the data. You could use dataclass as well, but I tend to just implement a normal class. One difference between dataclass and normal class is how the value of the class variable transfers to the instance value.
If you know you will only need one roww variable for your whole program then you can skip creating an instance and use the example that #mrblue6 suggested.

Related

Creat and access dictionary within class

I have created a class within tkinter that builds a frame and buttons (example below).
I am also trying to create a series of radiobuttons each with a command, and rather than having to type three times the same, I am trying to use a dictionary that stores the function name and the function (again see below):
import tkinter as tk
from tkinter import ttk
from tkinter import *
class EnoxDoseCalculator:
def __init__(self, root):
root.title('Enoxaparin Dose Calculator')
indication = {'VTE': self.vte, 'Atrial Fibrilation': self.af, 'Mechanical Heart valve': self.mech}
mainframe = ttk.Frame(root)
mainframe.grid(column = 0, row = 0, sticky='nsew')
test_button = ttk.Button(mainframe, text= 'Push Mew!!', command = self.format)
test_button.grid(row = 0, column = 0)
def vte(self):
pass
def af(self):
pass
def mech(self):
pass
def format(self):
self.var3 = tk.StringVar()
for key, value in self.indication.items():
ttk.Radiobutton(mainframe, text = self.indication.key, variable = self.var3, value = self.indication.key, command = self.indication.value).grid(row = n+1, column =0, sticky = 'nswe')
root = Tk()
EnoxDoseCalculator(root)
print(dir(EnoxDoseCalculator))
root.mainloop()
However, I keep getting this message:
I get this is to do with the dictionary functions being listed before they are created, so my question is, where do I place said dictionary, because I am sure this is possible. or is it that I am using the wrong names for the function?
Try this:
class EnoxDoseCalculator:
def __init__(self, root):
self.root = root # add this (mostly for consistency/best practice)
self.root.title('Enoxaparin Dose Calculator')
self.indication = {'VTE': self.vte, 'Atrial Fibrilation': self.af, 'Mechanical Heart valve': self.mech}
self.mainframe = ttk.Frame(self.root) # update this to include 'self'
self.mainframe.grid(column=0, row=0, sticky='nsew')
# update this to include 'self'
self.test_button = ttk.Button(self.mainframe, text='Push Mew!!', command=self.format)
self.test_button.grid(row=0, column=0)
Using self to namespace your class variables to EnoxDoseCalculator

Dynamically instantiate pages Tkinter

I'm building a GUI with code that originally came from another stack exchange answer. I've modified it since I want to pass variables to following pages and have them display the values. As a way to do this, rather than display on a button event, I am trying to create the page with the show() method:
import Tkinter as tk
#import vpnRename12 as Rename
#import vpnRename_pullconfig as pullConfig
"""
Create multiple pages to go through in the same frame. Use lift() to bring
the desired page into view and stack them on top of one another. Define
page-specific methods in each page.
"""
class vpnRenameProgram(tk.Tk):
"""
Create a page class that will allow you to lift to front the applicable
page with the proper functions to keep the script running.
"""
def __init__(self,*args,**kwargs):
tk.Tk.__init__(self,*args,**kwargs)
# Create an empty dictionary to contain all of the page names
self.frames = {}
self.show("MainView")
# Instantiate the next page on call, rather than at start to allow
flexibility
def instantiate_page(self, cls):
"""
Since all of the pages are defined in the same scope/namespace, we
can use
the globals()[] dict to find and instantiate the pages dynamically
with the show() method.
cls is the class argument we are doing a lookup on in the global()
dict.
"""
try:
newframe = globals()[cls](container,self)
page_name = newframe.__name__
except:
print("\nError defining inline class %s"%cls)#("Class %s is not defined" %cls)
newframe = None
page_name=globals()[cls].__name__
return newframe, page_name
# Create lift function to bring desired page to front of view,
#instantiate page if
# it isn't already (check frames dict)
def show(self, cls):
if cls not in self.frames.keys():
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
frame, page_name = self.instantiate_page(cls)
if frame==None:
frame = globals()[cls](parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="news")
frame = self.frames[cls]
frame.lift()
def get_page(self, classname):
"""
Return instance of page when it's class name is passed in as string
"""
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
class MainView(tk.Frame):
def __init__(self,parent, controller,**kwargs):
tk.Frame.__init__(self,parent)
self.controller = controller
self.edit_directory="edit_dir"
self.complete_directory ="comp_dir"
TitleLabel = tk.Label(self, text="VPN Script Editor")
TitleLabel.pack({"side":"top"})
EditLabel = tk.Label(self, text="Edit File Directory:
%s"%self.edit_directory)
EditLabel.pack()
CompLabel = tk.Label(self, text="Completed File Directory:
%s"%self.complete_directory)
CompLabel.pack()
Next = tk.Button(self, text="Next", command=lambda:
controller.show("listVPN"))
Next.pack()
class listVPN(tk.Frame):
"""
This is the second page, it contains a text box where you will list the
names of the vpn's that you want to edit. It will also display the
directories
obtained by the pullconfig script.
"""
def read_list(self):
vpn_list=str(self.var_vpn_list.get()).upper()
return vpn_list
def __init__(self, parent, controller, **kwargs):
self.controller = controller
tk.Frame.__init__(self, parent)
self.var_vpn_list = tk.StringVar()
label=tk.Label(self, text="Please list the VPNs desired to edit")
label.pack()
#Create text box to submit a list of vpns back to the main program
vpnLabel = tk.Label(self, text="VPN Names").pack()
self.TextBox = tk.Entry(self, textvariable=self.var_vpn_list)
self.TextBox.pack()
vpnListSubmit = tk.Button(self, text="Enter", command= lambda:
self.read_list() and self.controller.show("pickFiles"))
vpnListSubmit.pack()
class pickFiles(tk.Frame):
"""
Second page that allows you to select your desired files from the
edit directory specified in the config file. Check all desired files,
list will be returned to the program.
"""
def get_vpn_list(self):
list = self.controller.get_page("listVPN").var_vpn_list.get()
self.vpn_list = str(list).upper()
self.vpn_label.configure(text="VPN List: %s"%self.vpn_list)
return self.vpn_list
def __init__(self, parent, controller,**kwargs):
# Inherits from the tk.Frame class
tk.Frame.__init__(self, parent)
self.controller = controller
self.vpn_list = tk.StringVar()
list = self.controller.get_page("listVPN").var_vpn_list.get()
self.vpn_list = str(list).upper()
show_vpn = tk.Button(self, text="Show vpnlist", command =
self.get_vpn_list)
show_vpn.pack()
self.vpn_label = tk.Label(self, text="VPN List: %s" %self.vpn_list)
self.vpn_label.pack()
# todo: get external module function to run with variable input
#file_list = Rename.searchFile(vpnlist)
# Execute program on calling the parent class
if __name__=="__main__":
app = vpnRenameProgram()
app.mainloop()
EDIT:
Above is my whole code with custom scripts I've imported commented out. My main question is about layout. I want the frames to stack on top of one another, but they are not. Why is it doing this and what would get me on track to getting the layout I want?
The main problem with your code is that you're creating multiple containers. The code that served as a base for your program was specifically designed to have a single container with multiple frames within the container.
The first step is to create the container, and save a reference so it can be used later:
class vpnRenameProgram(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.container = tk.Frame(self)
self.container.pack(side="top", fill="both", expand=True)
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.show_frame("MainView")
I'm also going to recommend that you pass the actual class instead of a class name. This way you don't have to dig into globals() to try to find the right class based on the name.
Change the last line in the above to look like this:
self.show_frame(MainView)
You will also need to change get_page, but it's now a simple lookup:
def get_page(self, page_class):
return self.frames.get(page_class, None)
The final step is to redefine show to create the frame on demand. You've created a method called instantiate_page, but I see no real reason not to put it all in a single function since it's only a couple extra lines of code:
def show(self, page_class):
if page_class in self.frames:
frame = self.frames[page_class]
else
frame = page_class(parent=self.container, controller=self)
frame.grid(row=0, column=0, sticky="nsew")
self.frames[page_class] = frame
frame.tkraise()
That's all there is to it. You just need to remember to pass the class rather than the name of the class when calling show or get_page (eg: controller.show(listVPN), controller.get_page(pickFiles)`, etc)

Store class variable in dictionary

I am making a program that will have a box that receives two inputs, an event name and the time allocated for the event, and will later output a list of all the events that have been added. This is the class for the input boxes:
class agendaEvent:
def __init__(self, master):
self.frame = Frame(master, padx=10, pady=10)
self.frame.grid()
self.name = Entry(self.frame)
self.name.grid(row=1, column=0)
self.time = Entry(self.frame, width=10)
self.time.grid(row=1, column=1, padx=5)
I am fairly new to programming, but I believe the correct way to store the name and time would be in a dictionary.
There could be multiple instances of the class running at the same time and more could run after these have already been stored. It would be preferable to have them all stored in the same dictionary.
However, I do not know how to store the self variables in a dictionary that I could later access outside of the class. Is this possible? If so, how? If not, how could I store the variables in a way that they could be accessed in a separate function?
EDIT: This is the minimal code (I think) needed to replicate my issue:
from Tkinter import *
from functools import partial
class agendaEvent:
def __init__(self, master):
self.frame = Frame(master, padx=10, pady=10)
self.frame.grid()
self.name = Entry(self.frame)
self.name.grid(row=1, column=0)
self.time = Entry(self.frame, width=10)
self.time.grid(row=1, column=1, padx=5)
self.label1 = Label(self.frame, text="Event Name")
self.label1.grid(row=0, column=0)
self.label2 = Label(self.frame, text="Minutes")
self.label2.grid(row=0, column=1)
def addOne(master):
this_instance = agendaEvent(master)
def addEvent():
window = Toplevel()
addOneButton = Button(window, text='+1', command=partial(addOne, window))
addOneButton.grid(row=0, column=0, pady=5)
doneButton = Button(window, text='Done', command=partial(done, window))
doneButton.grid(row=1, column=0, pady=3)
def done(windowInstance):
#This is where I was thinking
#the code for storing information
#should be because it should store
#when the done button is pressed.
#This may not be a good way to do it,
#I don't know.
windowInstance.destroy()
windowInstance = ""
root = Tk()
addEventButton = Button(root, text="Add Events", command=addEvent)
addEventButton.pack()
root.mainloop()
I think what you need to do is store the agendaEvent instances you create in a list; then you can iterate over the list afterwards:
from Tkinter import *
from functools import partial
events = [] # place to store the instances
class agendaEvent:
...
def addOne(master):
events.append(agendaEvent(master)) # store the new instance in the list
def addEvent():
...
def done(windowInstance):
for event in events:
... # use event.time, event.date, etc.
windowInstance.destroy()
windowInstance = ""
root = Tk()
addEventButton = Button(root, text="Add Events", command=addEvent)
addEventButton.pack()
root.mainloop()
You may also need to clear the list in done, if this section can be called again.
You could use a class variable for storing data available outside the instance. Assuming you want to store names mapped to times you could add the following (this assumes that the Entry class is hashable):
class agendaEvent:
instances = dict() # Class variable, access with agendaEvent.instances
def __init__(self, master):
self.frame = Frame(master, padx=10, pady=10)
self.frame.grid()
self.name = Entry(self.frame)
self.name.grid(row=1, column=0)
self.time = Entry(self.frame, width=10)
self.time.grid(row=1, column=1, padx=5)
self.instances[self.name] = self.time
Now to access these values from elsewhere use:
agendaEvent.instances
E.g. if you have a name Entry stored in 'name' retrieve its time Entry with:
time = agendaEvent.instances[name]
If you want to store agenda events in a dictionary, you will have to serialize the agenda events object before storing it as the value and de-serialize the object when trying to access its name or time.
You could do this using a library such as Pickle or do it yourself using Json strings.
From your question, I'm not sure if this is what you want to do.

Visible textvariable in tkinter Entry widget

I'm having trouble with using the Entry box widget in tkinter. I want to have a new window to open when the user selects Edit -> Backbone... . In this window there will be a number of Entry widgets (just one coded for below for simplicity) that show default Strings stored in various instances the class Elements. The user should be able to edit this string and save it by clicking OK and returning it to its default value by clicking default. The entry box should always show the current value of the variable each time the backbone editor is reopened (If the whole program is restarted it does not need to remember the user input).
Upon opening the 'Backbone Editor' window the Entry box should show the string text variable but I can't make it appear.
from tkinter import *
from tkinter import ttk
class View(ttk.Frame):
"""Main GUI class"""
def __init__(self, master = None):
self.WIDTH = 450
self.HEIGHT = 500
self.lib = MolecularLibrary()
# Set up the main window
ttk.Frame.__init__(self, master, borderwidth=5, width=self.WIDTH, height=self.WIDTH)
self.master.resizable(FALSE, FALSE)
self.grid(column=0, row=0, sticky=(N, S, E, W))
self.columnconfigure(0, weight=1)
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_edit = Menu(self.menubar)
# Add the menus to the menubar and assign their variables
self.menubar.add_cascade(menu=menu_edit, label = "Edit")
### Edit ###
menu_edit.add_command(label="Backbone...", command=lambda : self.edit_backbone())
def edit_backbone(self):
"""Shows a window where the backbone constituents can be edited"""
backbone_window = Toplevel(borderwidth = 5)
backbone_window.title("Backbone Editor")
backbone_window.resizable(FALSE, FALSE)
print("sugar value", self.lib.sugar_var)
# LABELS FOR BACKBONE #
# Phosphate annotations and input
sugar_label = ttk.Label(backbone_window, text = "Sugar")
#inputs
sugar = ttk.Entry(backbone_window, textvariable = self.lib.sugar_var, justify = 'center', width=10)
### Buttons ###
default = ttk.Button(backbone_window, text = "Defaults", command=lambda : defaults())
okay = ttk.Button(backbone_window, text = "Okay", command=lambda : okay())
cancel = ttk.Button(backbone_window, text = "Cancel", command=lambda : backbone_window.destroy())
#content.grid(column=0, row=0)
sugar_label.grid(column=2, row=1)
sugar.grid(column=1, row=2, columnspan=3)
default.grid(column=0, row=12, columnspan=3, pady=2)
okay.grid(column=6, row=12, columnspan=3, pady=2)
cancel.grid(column=9, row=12, columnspan=4, pady=2)
backbone_window.focus()
def defaults():
"""Reset the backbone and bases to their defaults."""
self.lib.set_molecules()
def okay():
"""Set the backbone and base variables to the user set values."""
self.lib.sugar_var.new_formula(sugar.get())
backbone_window.destroy()
class MolecularLibrary:
"""
"""
def __init__(self, atom_file = r'C:\MyPyProgs\OSeq\resources\ATOMS.txt',
precision = 4):
self.molecules = {}
self.atom_file = atom_file
# self.molecule_file = molecule_file
# Variables
self.set_molecules()
def set_molecules(self):
"""
Set all of the molecules for the backbone and bases to their default values and forumlae.
"""
### Sugar ###
self.sugar_var = Element('C5H8O3', 'A')
def add_molecule(self, molecule):
"""(MolecularLibrary, list) -> None
Returns a dictionary of the molecule name as an Element
{molecule[0]: Element}
"""
print(molecule)
tmp = self.get_mass(molecule[1])
return {molecule[0]: Element(molecule[1], molecule[0], tmp[0], tmp[0])}
class Element:
"""
Creates an element with the following construct:
[symbol, name, monoisotopic, average]
"""
def __init__(self, symbol, name):
self.symbol = symbol
self.name = name
def __str__(self):
return str([self.symbol, self.name])
def get_name(self):
"""Returns the name of the Element"""
return self.name
def get_symbol(self):
"""Returns the symbol of the Element"""
return self.symbol
def new_formula(self, new_formula):
"""replace the formula with new_formaula and recalculate the
average and monoisotopic masses."""
self.symbol = new_formula
if __name__ == "__main__":
root = Tk()
root.title("Sequencer")
view = View(root)
root.mainloop()
The code above is a heavily stripped version of my program but has the same basic architecture. I'm afraid there is still quite a lot of code, I would normally try strip it down more, but I'm not sure if the issue I'm having is from the architecture or not.
In order to use the textvariable attribute, you must give it an instance of a tkinter variable: StringVar, IntVar, BooleanVar, or DoubleVar
A good starting point to learn more about these variables is here: http://effbot.org/tkinterbook/variable.htm

Can't get grid_remove() to work

In the app I'm working on I have a class setup to handle all my GUI. One method in this class creates a frame and populates a group of radiobutton's from a loop. This method will be called multiple times from outside the class to redraw this frame. The problem I have is that when the frame is redrawn it is actually just overwriting the existing frame (old frame persists). So if there are fewer options in a later call, the earlier options are still visible. I have tried doing a grid_remove on the frame first but can't get that to work. So my question is, why is the grid_remove() not working in the initMech() method below?
#!/usr/bin/env python
from Tkinter import *
class MWindow(Frame):
def __init__(self, parent) :
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
topframe = Frame(self.parent, bd=2, relief=GROOVE, padx=5, pady=5, width=300, height=50)
topframe.grid(row=0, column=0)
desc = Button(topframe, text="list1", command=lambda:set_list(1))
desc.grid(row=0,column=1)
desc2 = Button(topframe, text="list2", command=lambda:set_list(2))
desc2.grid(row=0,column=2)
def initMech(self):
try:
radio_frame.grid_remove()
except:
print "can't remove"
radio_frame = Frame(self.parent, bd=2, relief=GROOVE, padx=5, pady=5, width=300, height=50)
radio_frame.grid(row=1, column=0)
variant=StringVar()
c = 0
for x in chas.v_list:
Radiobutton(radio_frame, text=x,variable=variant, value=x, command=lambda x = x:chas.set_vari(x)).grid(row=0, column=c)
c = c+1
def onExit(self):
self.parent.destroy()
class Mech():
def set_chass(self,chass):
try:
if self.chassis == chass:
pass
else:
self.chassis = chass
del self.vari
except AttributeError:
self.chassis = chass
def load_vari(self):
if self.chassis == 1:
self.v_list = ["a","b","c"]
else:
self.v_list = ["w", "x", "y", "z"]
win.initMech()
def set_vari(self, vari):
self.vari = vari
def set_list(num):
chas.set_chass(num)
chas.load_vari()
root = Tk()
win = MWindow(root)
chas = Mech()
root.mainloop()
It is not working because you're using a local variable named radio_frame, and that variable isn't defined yet. Most likely, the error message you are ignoring is telling you exactly that. Why are you trying to remove a frame that you haven't yet created?
Also, are you aware that grid_remove only removes the widget from view, it doesn't destroy it? Thus, if you call this function several times you'll have several invisible versions of this frame, all using up memory.

Categories