How to reference "not yet created" class instance in Python - python

New to python and generally object-oriented programming. I have written a script to do some stuff and here is a very simplified version of it with its main structure.
from tkinter import *
import matplotlib.pyplot as plt
import numpy as np
class MainWindow:
def __init__(self):
self.mw = Tk()
button = Button(self.mw, text='Open', command=self.openfile)
button.pack()
self.anotherwd = []
self.mw.mainloop()
def openfile(self):
x = float(input('Number:'))
self.anotherwd.append(AnotherWindow(x))
class AnotherWindow:
def __init__(self,number):
self.aw = Toplevel()
self.var = IntVar()
self.numb = number
label = Label(self.aw, text="Multiply by:")
entry = Entry(self.aw, textvariable=self.var)
button = Button(self.aw, text='Plot', command=self.plot)
label.pack()
entry.pack()
button.pack()
def plot(self):
numb_change = self.numb * self.var.get()
plotwd.plotvar = numb_change
plotwd.plot_line()
YetAnotherWindow()
class PlotWindow:
def __init__(self):
self.plotvar = 0
self.fig, self.ax = plt.subplots()
def plot_line(self):
example = np.arange(0., 5., 0.2)
self.ax.plot(example, self.plotvar*example)
self.fig.show()
class YetAnotherWindow:
def __init__(self):
self.yaw = Toplevel()
self.var_yaw = IntVar()
label = Label(self.yaw, text="More numbers:")
entry = Entry(self.yaw, textvariable=self.var_yaw)
button = Button(self.yaw, text='Plot more', command=self.plot_another_line)
label.pack()
entry.pack()
button.pack()
def plot_another_line(self):
example = np.arange(0., 5., 0.2)
plotwd.ax.plot(example, self.var_yaw.get() * plotwd.plotvar * example)
plotwd.fig.show()
plotwd = PlotWindow()
gui = MainWindow()
In the 'MainWindow' it let's you import some data (here as a number as console-input) then in a 'AnotherWindow' you modify the data (here multiply it by a entered number) and it plots this modified data in a 'PlotWindow'. Afterwards you can do some stuff with it in 'YetAnotherWindow' (again some useless multiplication) and plot the result in the same 'PlotWindow'.
The problem is, that there is only one 'PlotWindow'-Object while I want to create an instance every time I press the button in 'AnotherWindow' with separate modified data and another 'YetAnotherWindow'-Instance.
Now I could do it like in 'MainWindow' where I append new instances to a list but then I don't know how to reference such an instance in a separate class like 'YetAnotherWindow', as the one attribute in that instance of 'PlotWindow' would not be created yet while initialising. I wan which is why I declared it "fixed" as 'plotwd' in the first place as a "workaround".
I think "inheritance" might be a relevant keyword but I can't get it to work that way. I might doing some things too complicated anyway, so I better ask before I do some massive rewriting in the actual code.
Thanks.
Edit 1: Also a (related?) problem is that if I close the plot window 'AnotherWindow' closes too and I REALLY don't want that, which is the primary reason I want to change things up.
Edit 2: Not working example how I imagine it would work.
from tkinter import *
import matplotlib.pyplot as plt
import numpy as np
class MainWindow:
def __init__(self):
self.mw = Tk()
button = Button(self.mw, text='Open', command=self.openfile)
button.pack()
self.anotherwd = []
self.mw.mainloop()
def openfile(self):
x = float(input('Number:'))
self.anotherwd.append(AnotherWindow(x))
class AnotherWindow:
def __init__(self, number):
self.aw = Toplevel()
self.var = IntVar()
self.numb = number
self.plotwindows = []
self.yawindows = []
label = Label(self.aw, text="Multiply by:")
entry = Entry(self.aw, textvariable=self.var)
button = Button(self.aw, text='Plot', command=self.plot)
label.pack()
entry.pack()
button.pack()
def plot(self):
numb_change = self.numb * self.var.get()
self.plotwindows.append(PlotWindow)
self.plotwindows[-1].plotvar = numb_change
self.plotwindows[-1].plot_line
self.yawindows.append(YetAnotherWindow)
class PlotWindow:
def __init__(self):
self.plotvar = 0
self.fig, self.ax = plt.subplots()
def plot_line(self):
example = np.arange(0., 5., 0.2)
self.ax.plot(example, self.plotvar*example)
self.fig.show()
class YetAnotherWindow():
def __init__(self):
self.yaw = Toplevel()
self.var_yaw = IntVar()
label = Label(self.yaw, text="More numbers:")
entry = Entry(self.yaw, textvariable=self.var_yaw)
button = Button(self.yaw, text='Plot more', command=self.plot_another_line)
label.pack()
entry.pack()
button.pack()
def plot_another_line(self):
example = np.arange(0., 5., 0.2)
gui.anotherwd[-1].plotwindows[-1].ax.plot(example, self.var_yaw.get() * gui.anotherwd[-1].plotwindows[-1].plotvar * example)
gui.anotherwd[-1].plotwindows[-1].fig.show()
gui = MainWindow()
Also even if that would work, I would acess in "YetAnotherGui" the "last PlotWindow" and not the one it "belongs to".

Related

Getting input from another window in tkinter

I am having a little trouble with this project that I am working on. My project is this GUI application. In my test.py file, I call another file that contains instructions for another GUI window. This is where I am having trouble. In the test.py file, if you click run, a small window will appear. Click TEST in the small window. Then another window will appear that contains text fields if you enter numbers into the text fields for the window and then click enter. My IDE gets these error messages. It says that " ValueError: could not convert string to float: ' ' " My question is how do I fix this so that I do not get this error message? It is supposed to print the input that was entered into the window. I have two files, test.py, and model_objects.py. If you run model_objects.py by itself, it works perfectly. But when I try to import this file into test.py, it does not want to work right. This is programmed in Python. Also, my model_objects.py file is placed in a folder called util in the project. The values that I entered are floating-point values. I am having trouble with this. If you can help, I would greatly appreciate it.
Here is my code:
model_objects.py (This is in a folder called util in the project.)
import tkinter as tk
from tkinter import ttk
from tkinter.ttk import Style
import numpy as np
from util import InputData
class Harmonic_Oscillator:
def __init__(self):
self.type = 0
self.name = "Harmonic Oscillator"
self.nparam = 2
self.label = ["\u03BC", "k"]
self.param = np.zeros(self.nparam, float)
def set_param(self, param_list):
for i in range(self.nparam):
self.param[i] = param_list[i]
return
class Morse_Oscillator:
def __init__(self):
self.type = 1
self.name = "Morse Oscillator"
self.nparam = 3
self.label = ["\u03BC", "De", "a"]
self.param = np.zeros(self.nparam, float)
def set_param(self, param_list):
for i in range(self.nparam):
self.param[i] = param_list[i]
return
class Test_Oscillator:
def __init__(self):
self.type = 2
self.name = "Test Oscillator"
self.nparam = 4
self.mu = 0
self.label = ["a", "b", "c", "d"]
self.param = np.zeros(self.nparam, float)
def set_param(self, param_list):
for i in range(self.nparam):
self.param[i] = param_list[i]
return
def model_prompt(potential_model):
window1 = tk.Tk()
style = Style()
window1.title('PyFGH Parameters')
box_length = 103
for q in range(3):
box_length = box_length + 33 * potential_model[q].nparam
box_len_str = '300x' + str(box_length)
window1.geometry(box_len_str)
entries = []
qvar = np.empty(3, dtype=list)
for i in range(3):
qvar[i] = []
j = 0
y = 5
for q in range(3):
for qparam in range(potential_model[q].nparam):
qvar[q].append(tk.StringVar())
ttk.Label(window1, text=potential_model[q].label[qparam] + " for Q:" + str(q + 1) + ":",
font=("Times New Roman", 15)).place(x=50, y=y)
# set text variable as q1var[j] , each entry will have separate index in the list
a1 = ttk.Entry(window1, textvariable=qvar[q][qparam], font=("Times New Roman", 10)).place(x=140, y=y)
j += 1
y += 35
def enter_button():
for q in range(3):
param_list = []
for qparam in range(potential_model[q].nparam):
param_list.append(qvar[q][qparam].get())
potential_model[q].set_param(param_list) # This is giving me error. Not working properly!!!
for q in range(3):
for qparam in range(potential_model[q].nparam):
print(potential_model[q].param[qparam])
InputData.output.items.model_data = potential_model
print(InputData.output.items.model_data)
window1.destroy()
enter = tk.Button(window1, text='Enter', bd='20', bg='green', fg='white',
command=enter_button).place(x=110, y=y)
window1.mainloop()
def output2():
sections = []
for i in range(3):
if InputData.output.items.v[i] == "Model-Harmonic Oscillator":
sections.append(Harmonic_Oscillator())
elif InputData.output.items.v[i] == "Model-Morse Oscillator":
sections.append(Harmonic_Oscillator())
elif InputData.output.items.v[i] == "Model-Test Oscillator":
sections.append(Harmonic_Oscillator())
#test = [Harmonic_Oscillator(), Morse_Oscillator(), Test_Oscillator()]
#model_prompt(test)
Here is another file called test.py
from util import InputData
from util import model_objects
from util import model_objects
from util.model_objects import Harmonic_Oscillator, Morse_Oscillator, Test_Oscillator, model_prompt
import tkinter as tk
def write_slogan():
test = [Harmonic_Oscillator(), Morse_Oscillator(), Test_Oscillator()]
model_prompt(test)
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
button = tk.Button(frame,
text="QUIT",
fg="red",
command=quit)
button.pack(side=tk.LEFT)
slogan = tk.Button(frame,
text="TEST",
command=write_slogan)
slogan.pack(side=tk.LEFT)
root.mainloop()
It's a bit esoteric, but the issue is this line in your model_prompt function:
qvar[q].append(tk.StringVar())
The quick fix would be:
qvar[q].append(tk.StringVar(window1))
The tkinter variable types' constructors accept an optional parameter - a handle to the window to which the associated entry widget is attached. If none is supplied, by default, it will pick the first tk.Tk window that was instantiated (in your case, that was root in test.py). The StringVars will update themselves whenever the event queue of the bound window has cleared. Since you spawned a new tk.Tk, this interrupted your root event queue, and since your variables were bound to root, they did not update despite text being entered in the entries.
If you wanted to be super proper, you should look into tk.Toplevel or tkinter.simpledialog to spawn child windows. Your program should never have more than one tk.Tk window in the first place.
i made a little example, i hope it matches yourr mainproblem. if you want to work with classes, in the most cases you need references.
from tkinter import *
class MainWindow(Tk):
def __init__(self):
super(MainWindow, self).__init__()
self.test = Test(self) # test class import
self.outputLbl = Label(self) # output Label
self.outputLbl.pack(side="top", fill="x", ipady=20)
class Test(Frame):
def __init__(self, parent):
super(Test, self).__init__()
self.parent = parent # you can use this way to call between classes
self._input = Entry(self.parent)
self._input.pack()
self._input.bind("<Return>", self.outputMW)
def outputMW(self, event): # function, when pressing return it gives changes the text in your label
var = self._input.get()
self.parent.outputLbl.config(text=var) # self.parent makes the reference to your other class
if __name__ == '__main__':
mw = MainWindow()
mw.geometry("500x500")
mw.mainloop()

Creating two windows using Tkinter and getting a name from the second window

I'm trying to create an app with Tkinter which requires the user to hit the button of the first window and then a new window will appear where they'll write their name.
But i when i try to get the name, i always end up with an empty string.
Here's my code:
from tkinter import *
class first_class(object):
def __init__(self, window):
self.window = window
b1 = Button(window, text = "first_get", command = self.get_value_2)
b1.grid(row = 0, column = 1)
def get_value_2(self):
sc = Tk()
second_class(sc)
sc.mainloop()
class second_class(object):
def __init__(self, window):
def get_value_1():
print(self.name.get())
self.window = window
self.name = StringVar()
self.e1 = Entry(window, textvariable = self.name)
self.e1.grid(row = 0, column = 0)
b1 = Button(window, text = "second_get", command = get_value_1)
b1.grid(row = 0, column = 1)
window = Tk()
first_class(window)
window.mainloop()
What should i do to get the name properly?
Generally speaking, you should avoid calling Tk() more than once within a tkinter application. It's also hardly ever necessary to call mainloop() more than once.
Your code with the changes indicated below shows how to do this. Note that I also renamed and reformatted a few things so it follows the recommendations in PEP 8 - Style Guide for Python Code more closely — which I highly recommend you read and start following.
import tkinter as tk
class FirstClass(object):
def __init__(self, window):
self.window = window
b1 = tk.Button(window, text="first_get", command=self.get_value_2)
b1.grid(row=0, column=1)
def get_value_2(self):
# sc = tk.Tk() # REMOVED
SecondClass(self.window) # CHANGED
# sc.mainloop() # REMOVED
class SecondClass(object):
def __init__(self, window):
self.window = window
self.name = tk.StringVar()
self.e1 = tk.Entry(window, textvariable=self.name)
self.e1.grid(row=0, column=0)
def get_value_1():
print('self.name.get():', self.name.get())
b1 = tk.Button(window, text="second_get", command=get_value_1)
b1.grid(row=0, column=1)
window = tk.Tk()
FirstClass(window)
window.mainloop()

Creating a dynamic variable and use it further in python (tkinter) [duplicate]

I'm trying to avoid to multiply functions in code by using
def Return_Label(self,number)
with a parameter.
Any Idea how to use string in order to define variable name usable to .set value to StringVar()?
Example code below:
import tkinter as tk
from tkinter import *
class WINDOW():
def __init__(self):
self.Settings_Window()
def Settings_Window(self):
self.settings_window = tk.Tk()
self.settings_window.minsize(200,200)
self.entry = Entry(self.settings_window)
self.entry.pack()
self.entry2 = Entry(self.settings_window)
self.entry2.pack()
self.label1input = StringVar()
self.label = Label(self.settings_window,textvariable=self.label1input, bg='yellow')
self.label.pack(expand='yes',fill='x')
self.label2input = StringVar()
self.label2 = Label(self.settings_window, textvariable=self.label2input, bg='yellow')
self.label2.pack(expand='yes', fill='x')
self.button = Button(self.settings_window,text='SETUP1',command=self.Next)
self.button.pack()
self.button2 = Button(self.settings_window,text='SETUP2',command=self.Next2)
self.button2.pack()
self.settings_window.mainloop()
def Next(self):
self.number=1
self.Return_Label(self.number)
def Next2(self):
self.number=2
self.Return_Label(self.number)
def Return_Label(self,number):
self.entry_field_value = self.entry.get()
print(self.entry_field_value)
#self.label1input.set(self.entry_field_value)
setattr(self,'label'+str(number)+'input.set',self.entry_field_value)
window=WINDOW()
I prefer a list approach to managing multiple entry fields and updating values.
By using list you can use the index value to manage the labels as well :D.
See the below example of how you can use list to deal with all the values and updates.
import tkinter as tk
from tkinter import *
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.minsize(200, 200)
self.entry_list = []
self.label_list = []
entry_count = 2
for i in range(entry_count):
self.entry_list.append(Entry(self))
self.entry_list[i].pack()
for i in range(entry_count):
self.label_list.append(Label(self,bg='yellow'))
self.label_list[i].pack(expand='yes', fill='x')
Button(self, text='SETUP', command=self.Return_Label).pack()
def Return_Label(self):
for ndex, lbl in enumerate(self.label_list):
lbl.config(text=self.entry_list[ndex].get())
if __name__ == '__main__':
Window().mainloop()
Create lists of objects rather than individual attributes for each object. For example,
import tkinter as tk
from tkinter import *
class Window:
def __init__(self):
self.settings_window()
def Settings_Window(self):
self.settings_window = tk.Tk()
self.settings_window.minsize(200,200)
self.entries = [
Entry(self.settings_window),
Entry(self.settings_window)
]
for e in self.entries:
e.pack()
self.labelinputs = [
StringVar(),
StringVar()
]
self.labels = [
Label(self.settings_window, textvariable=label, bg='yellow')
for label in self.labelinputs
]
for l in self.labels:
l.pack(expand='yes', fill='x')
self.buttons = [
Button(self.settings_window,text='SETUP1',command=lambda: self.return_label(0))
Button(self.settings_window,text='SETUP2',command=lambda: self.return_label(1))
]
for b in self.buttons:
b.pack()
self.settings_window.mainloop()
def return_label(self,number):
entry_field_value = self.entry.get()
self.labelsinput[number].set(entry_field_value)
window=WINDOW()
Dynamicly computing variable names should be avoided at all costs. They are difficult to do correctly, and it makes your code hard to understand, hard to maintain, and hard to debug.
Instead, store the widgets in a dictionary or list. For example:
def __init___(self):
...
self.vars = {}
...
self.vars[1] = StringVar()
self.vars[2] = StringVar()
...
def Return_Label(self,number):
self.entry_field_value = self.entry.get()
var = self.vars[number]
var.set(self.entry_field_value)
Though, you really don't need to use StringVar at all -- they usually just add extra overhead without providing any extra value. You can save the labels instead of the variables, and call configure on the labels
self.labels[1] = Label(...)
...
self.labels[number].configure(text=self.entry_field_value)

Passing a variable from combobox to called function

I am trying to pass a variable from a tkinter combobox to a function called when clicking a 'Run' button. I am relatively new to python and every option I have tried creates an error - mostly that the variable is not defined. I believe this is because I am not defining it in the correct place. Any help is greatly appreciated.
from tkinter import *
from tkinter import ttk
from URL_Generator import crawl_site
listFile = open('regions1.txt','r')
root = Tk()
root.configure()
varItems = StringVar(root, value='')
class MainWindow(Frame):
def __init__(self,master = None):
Frame.__init__(self,master)
self.master = master
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create Window Layout"""
self.label = Label(self, text="List Items").pack()
self.itemCombo = ttk.Combobox(self, width = 16, textvariable = varItems)
self.itemCombo.bind("<Return>", self.itemCombo_onEnter)
self.itemCombo.bind('<<ComboboxSelected>>',self.itemCombo_onEnter)
self.itemCombo['values'] = [l.strip() for l in listFile.readlines()]
self.itemCombo.pack()
self.blank = Label(self,text='').pack()
"""I want to pass the value selected in the combobox to the crawl_region() function when pushing Run"""
self.RunButton = Button(self, text="Run",command = crawl_site.crawl_region(region))
self.RunButton.pack()
def itemCombo_onEnter(self,event):
varItems.set(varItems.get().lower().strip())
mytext = varItems.get().strip()
vals = self.itemCombo.cget('values')
self.itemCombo.select_range(0,END)
print(mytext)
region = mytext
"""I want to pass mytext to the function called when pushing Run"""
if not vals:
self.itemCombo.configure(values = (mytext,))
elif mytext not in vals:
with open('regions1.txt', 'w') as f:
self.itemCombo.configure(values=vals + (mytext,))
f.write("\n".join(vals + (mytext,)))
f.close()
return 'break'
app = MainWindow(root)
root.mainloop()
Sample function called (crawl_site.crawl_region()):
class crawl_site():
def crawl_region(region):
print('passed region '+ str(region))
passed region [] is immediately returned, but nothing happens when I make a selection or press the Run button.
Try the below code.
I've created a class property self.mytext which is set when the combo button is entered itemCombo_onEnter. When the button is pressed the onRunButton function is called. if self.mytext has been set, it will call the crawl_region function with self.mytext as an argument.
from tkinter import *
from tkinter import ttk
from URL_Generator import crawl_site
listFile = open('regions1.txt','r')
root = Tk()
root.configure()
varItems = StringVar(root, value='')
class MainWindow(Frame):
def __init__(self,master = None):
Frame.__init__(self,master)
self.master = master
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create Window Layout"""
self.label = Label(self, text="List Items").pack()
self.itemCombo = ttk.Combobox(self, width = 16, textvariable = varItems)
self.itemCombo.bind("<Return>", self.itemCombo_onEnter)
self.itemCombo.bind('<<ComboboxSelected>>',self.itemCombo_onEnter)
self.itemCombo['values'] = [l.strip() for l in listFile.readlines()]
self.itemCombo.pack()
self.blank = Label(self,text='').pack()
"""I want to pass the value selected in the combobox to the crawl_region() function when pushing Run"""
self.RunButton = Button(self, text="Run",command = self.onRunButton)
self.RunButton.pack()
def onRunButton(self):
if self.mytext:
crawl_site.crawl_region(self.mytext)
def itemCombo_onEnter(self,event):
varItems.set(varItems.get().lower().strip())
mytext = varItems.get().strip()
vals = self.itemCombo.cget('values')
self.itemCombo.select_range(0,END)
print(mytext)
self.mytext = mytext
"""I want to pass mytext to the function called when pushing Run"""
if not vals:
self.itemCombo.configure(values = (mytext,))
elif mytext not in vals:
with open('regions1.txt', 'w') as f:
self.itemCombo.configure(values=vals + (mytext,))
f.write("\n".join(vals + (mytext,)))
f.close()
return 'break'
app = MainWindow(root)
root.mainloop()
Your code wasn't working because of this line
self.RunButton = Button(self, text="Run",command = crawl_site.crawl_region(region))
This immediately calls the method crawl_region with the region as an argument and tries to set the callback of the button to the result of that method.
Another way to 'fix' your problem without creating another function would be to use lambda but I think my method is more readable.

Python, Tkinter library, Attribute Error in Object involving GUI

I'm making a very simple program for class that involves multiplying the number of a GUI slider by another number of another GUI slider. But, for some reason when I run the program now, I get an AttributeError saying that 'gui' object has no attribute 'slider1'. Any ideas? Here's the code:
import tkinter
import random
class gui:
def __init__(self):
self.main_window = tkinter.Tk()
#widgets
self.__canvas = tkinter.Canvas(self.main_window,bg='white',width=300,height=10)
self.label = tkinter.Label(self.main_window,text=('Product:',0))
self.slider1 = tkinter.Scale(self.main_window,from_=0, to=12)
self.slider2 = tkinter.Scale(self.main_window,from_=0, to=12)
#packs
self.__canvas.pack()
self.label.pack(side='top')
self.slider1.pack(side='left')
self.slider2.pack(side='right')
self.button = tkinter.Button(self.main_window,text='Click to multiply',command=self.multiply())
self.button.pack(side='bottom')
tkinter.mainloop()
def multiply(self):
x = int(self.slider1.get())
y = int(self.slider2.get())
num = x*y
self.label.config(text=('Product:',num))
gui()
There is a few syntax error in the program, I commented those. As well as you should put orientations on the scales. Here is the code.
import tkinter as tk
class gui:
def __init__(self):
self.root = tk.Tk()
# the widgets
self.button = tk.Button(self.root, text="Multiply!", command=self.multiply)
# you need no '()' for the function when inputing it in tkinter.
self.label = tk.Label(self.root, text="Product: 0") # the '0 must be a string
self.sliderX = tk.Scale(self.root, from_=0, to=12, orient=tk.HORIZONTAL)
self.sliderY = tk.Scale(self.root, from_=0, to=12, orient=tk.VERTICAL)
# add an orient to the scales.
# now pack the widgets.
self.button.pack()
self.label.pack()
self.sliderX.pack()
self.sliderY.pack()
def multiply(self):
x = int(self.sliderX.get())
y = int(self.sliderY.get())
num = str(x * y) # need to turn the int to a string.
self.label.config(text="Product: "+num)
app = gui()
app.root.mainloop()
The reason it isn't working for you is because there is no instance of the program. This is what I do at the very end. Python's garbage collecting collects the instance made with gui() and so Tkinter can't reference an instance of the class.

Categories