Delivering data between instances of different classes in tkinter - python-3.x

Beginner to tkinder and the version of python is 3.6.
I'm trying to make a python GUI for data processing.
There're several instances of different classes designed for different jobs.
When the data is processed with function click_emd in EMDFrame class, it should be drawn in PreviewFrame class which uses matplotlib to show the data. However i have no idea how to pass the data between two classes.
I've searched similar questions but they didn't work.
Now I'm thinking about two possible solutions.
One is to find a way to pass data_processed to previewframe.cplot in emdframe.click_emd.
Another is to make full use MainPage class. It can acquire data_processed from one class and call the cplot in another class to drawn. But how can MainPage get noticed once the data_processed is generated?
Got confused and don't know what to do. Really appreciate for your patience.
There are two py code file.
Mainframe.py :
import tkinter as tk
from view import * # initiate the subframes
class MainPage():
def __init__(self, master=None):
self.root = master
root.geometry('%dx%d' % (800, 600))
self.createPage()
def createPage(self):
self.emdPage = EMDFrame(root) # subframe
self.emdPage.place(x=20, y=300)
self.preview = PreviewFrame(root)
self.preview.place(x=340, y=20)
self.aboutPage = AboutFrame(root)
menubar = Menu(root)
menubar.add_command(label='EMD', command=self.emd)
menubar.add_command(label='About', command=self.aboutDisp)
root['menu'] = menubar # set menu
def emd(self):
self.emdPage.place(x=20, y=300)
self.preview.place(x=340, y=20)
self.aboutPage.place_forget()
def aboutDisp(self):
self.emdPage.place_forget()
self.preview.place_forget()
self.aboutPage.place(x=20, y=300)
def plot_update(self, data, t):
self.preview.cplot(data, t)
root = tk.Tk()
root.title("Demo")
app = MainPage(root)
root.mainloop()
view.py :
from tkinter import *
from pyhht.emd import EMD
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
data = []
class EMDFrame(LabelFrame):
def __init__(self, master=None):
LabelFrame.__init__(self, master, width=300, height=250, text='EMD')
self.root = master
self.createPage()
def createPage(self):
b = Button(self, text='EMD', width=20, height=2, command=self.click_emd)
b.place(x=40, y=160, anchor='nw', width=80, height=40)
def click_emd(self):
global data
decomposer = EMD(data)
data_processed = decomposer.decompose()
# Deliever data_processed to PreviewFrame's cplot function
class PreviewFrame(LabelFrame):
def __init__(self, master=None):
LabelFrame.__init__(self, master, width=440, height=530, text='Preview')
self.root = master
self.createPage()
def cplot(self, data, t):
f = Figure(figsize=(4, 4.8), dpi=100)
a = f.add_subplot(111)
a.plot(t, data)
canvas = FigureCanvasTkAgg(f, master=self)
canvas.draw()
canvas.get_tk_widget().place(x=20, y=0)
def createPage(self):
t = np.arange(0.0, 3, 0.01)
s = np.sin(2 * np.pi * t)
self.cplot(s,t)
class AboutFrame(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.root = master
self.createPage()
def createPage(self):
Label(self, text='About').pack()

You aren't passing data between classes, you are passing data between two instances of classes.
In order for one instance to send data to another instance of a class, it needs to know about it. So in MainFrame.py we pass the reference of the preview class to the emdPage.
def createPage(self):
self.preview = PreviewFrame(root)
self.preview.place(x=340, y=20)
self.emdPage = EMDFrame(root,self.preview) # subframe
self.emdPage.place(x=20, y=300)
self.aboutPage = AboutFrame(root)
Then in the other view.py, update the class to keep track of the preview reference and then use it once the data has been processed.
class EMDFrame(LabelFrame):
def __init__(self, preview, master=None):
LabelFrame.__init__(self, master, width=300, height=250, text='EMD')
self.preview = preview
self.root = master
self.createPage()
def createPage(self):
b = Button(self, text='EMD', width=20, height=2, command=self.click_emd)
b.place(x=40, y=160, anchor='nw', width=80, height=40)
def click_emd(self):
global data
decomposer = EMD(data)
data_processed = decomposer.decompose()
# Deliever data_processed to PreviewFrame's cplot function
self.preview.cplot(.....)
(Code example is untested and incomplete but should show the principle)
(There is probably a better way to do it thought the MainPage class but this would involve a bit more code.)

Related

Tkinter gives me a second window

I am writing code for a tkinter gui using a class, however I notice that when I run there is a second window besides the main one I made. I've tried a number of things but they either break the code or the window is black. See code below.
import tkinter as gui
class loginWindow(gui.Frame):
def __init__(self):
super(loginWindow, self).__init__()
self.logUI()
def logUI(self):
self.mainWindow = gui.Tk()
self.mainWindow.title("GLSC IT Inventory")
self.mainWindow.minsize(400, 150)
self.mainWindow.maxsize(400, 150)
self.mainWindow.geometry("400x150")
self.greet_label = gui.Label(self.mainWindow, text="Welcome!!!")
self.greet_label.place(x=180, y=5)
self.uname_label = gui.Label(self.mainWindow, text="Username:")
self.uname_label.place(x=10, y=24)
self.uname_input = gui.StringVar()
self.uname_field = gui.Entry(self.mainWindow, bd=4, textvariable=self.uname_input)
self.uname_field.place(x=80, y=25, width=160)
self.pwd_label = gui.Label(self.mainWindow, text="Password:")
self.pwd_label.place(x=10, y=54)
self.pwd_input = gui.StringVar()
self.pwd_field = gui.Entry(self.mainWindow, bd=4, textvariable=self.pwd_input, show="\u2022")
self.pwd_field.place(x=80, y=55, width=160)
self.login_button = gui.Button(self.mainWindow, text="Login", command=None)
self.login_button.place(x=180, y=95)
my_app = loginWindow()
my_app.mainloop()
When you create instance of loginWindow(), an instance of Tk() is required but there is none, so it will be created implicitly for you.
Then another instance of Tk() is created inside logUI(). So there are two instances of Tk().
One way to fix it is loginWindow not inherited from Frame:
class loginWindow:
def __init__(self):
self.logUI()
def logUI(self):
...
# add for calling tkinter.mainloop()
def mainloop(self):
self.mainWindow.mainloop()

Using dynamically added widgets in PyQt/Pyside

I have modified the answer given here as written below. The code is basically creating pushbuttons with a counter as pushButton_0, pushButton_1..
Here, I know that when I press to self.addButton I am creating widgets named like self.pushButton_0, self.pushButton_1 etc. So, my question is, how I'm supposed to use this pushbuttons? Because when I'm trying to do something like self.pushButton_0.clicked.connect(self.x), it' s telling me that "there is no attribute named 'pushButton_0'".
Thanks!
from PyQt4 import QtGui, QtCore
import sys
class Main(QtGui.QMainWindow):
def __init__(self, parent = None):
super(Main, self).__init__()
self.GUI()
def GUI(self):
self.count = 0
# main button
self.addButton = QtGui.QPushButton('button to add other widgets')
self.addButton.clicked.connect(self.addWidget)
# scroll area widget contents - layout
self.scrollLayout = QtGui.QFormLayout()
# scroll area widget contents
self.scrollWidget = QtGui.QWidget()
self.scrollWidget.setLayout(self.scrollLayout)
# scroll area
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.scrollWidget)
# main layout
self.mainLayout = QtGui.QVBoxLayout()
# add all main to the main vLayout
self.mainLayout.addWidget(self.addButton)
self.mainLayout.addWidget(self.scrollArea)
# central widget
self.centralWidget = QtGui.QWidget()
self.centralWidget.setLayout(self.mainLayout)
# set central widget
self.setCentralWidget(self.centralWidget)
def addWidget(self):
self.scrollLayout.addRow(Test(self))
self.count = self.count + 1
print(self.count)
class Test(QtGui.QWidget):
def __init__( self, main):
super(Test, self).__init__()
self.Main = main
self.setup()
def setup(self):
print(self.Main.count)
name = "pushButton_"+str(self.Main.count)
print(name)
self.name = QtGui.QPushButton('I am in Test widget '+str(self.Main.count))
layout = QtGui.QHBoxLayout()
layout.addWidget(self.name)
self.setLayout(layout)
app = QtGui.QApplication(sys.argv)
myWidget = Main()
myWidget.show()
app.exec_()
After hours, I found the problem!
You have to declare the signal while creating the pushbutton!
To fix this, I rewrote the setup function as below;
def setup(self):
print(self.Main.count)
name = "pushButton_"+str(self.Main.count)
print(name)
self.name = QtGui.QPushButton('I am in Test widget '+str(self.Main.count))
self.name.clicked.connect(self.x) # self.x is any function
layout = QtGui.QHBoxLayout()
layout.addWidget(self.name)
self.setLayout(layout)
So know, you will run function x whenever you push the new created pushbuttons!

tkinter widget call back to identify details about the widget

I was reading http://infohost.nmt.edu/tcc/help/pubs/tkinter/tkinter.pdf
and started playing around with "54.7. The extra arguments trick" located towards the end of the document. If I understand it correctly, I can create widgets in a list, so that when the widget is clicked on, the callback should be able to display information stored in the instantiated class, I get the widget to display, but the callback is not displaying the info that I expected. IE the attributes of the object created - Hopefully someone can help. thanks in advance
here is the code
from tkinter import *
class Component(object):
def __init__(self, image=None, Number=None, Name=None):
self.image=image
self.Number=Number
self.Name=Name
ComponentList = [] #array of componenents
def FeederCB(event):
print(event.widget.Number, event.widget.Name)
root = Tk()
test=Frame(root, bg='white')
test.grid()
for x in range(0, 2):
print(x)
ComponentList.append(Component(None, str(x),"Poly 23"))
Temp=Label(test, text=ComponentList[-1].Name)
Temp.configure(bg='white', font='times 12')
Temp.grid(row=0, column=x, sticky=S)
ComponentList[-1].image = (Label(test, text='test'))
ComponentList[-1].image.configure(bg='white')
ComponentList[-1].image.bind("<Button-1>",FeederCB)
ComponentList[-1].image.grid(row=1, column=x)
print('Lenght of Component List ', len(ComponentList))
root.mainloop()
I think I got it
from tkinter import *
class Component(object):
def __init__(self, image=None, Number=None, Name=None):
self.image=image
self.Number=Number
self.Name=Name
ComponentList = [] #array of componenents
#ComponentList.append(Class object,class attibutes)
#example ComponentList.append(Virgin("1", "materialtype", "Name", "Setpoint"))
#print(ComponentList[0].Name)
def __FeederCB(event, x):
print(ComponentList[x].Number, ComponentList[x].Name)
root = Tk()
test=Frame(root, bg='white')
test.grid()
for x in range(0, 2):
print(x)
ComponentList.append(Component(None, str(x),"Poly 23"))
Temp=Label(test, text=ComponentList[-1].Name)
Temp.configure(bg='white', font='times 12')
Temp.grid(row=0, column=x, sticky=S)
ComponentList[-1].image = (Label(test, text='test'))
ComponentList[-1].image.configure(bg='white')
def FeederCB(event, x=x):
return __FeederCB(event, x)
ComponentList[-1].image.bind("<Button-1>",FeederCB)
ComponentList[-1].image.grid(row=1, column=x)
print('Lenght of Component List ', len(ComponentList))
root.mainloop()

in Python3/tkinter is it possible to create a toolbar into a separate class, but still allowing it to interact with the main app?

I am about to begin a new Python3/tkinter project and I want to make sure that I get as much code out of my way as possible. I am creating an app that will have, for now, one window composed of 3 areas:
Toolbar
Center/main area
Statusbar
I am trying to keep the main app class as clean as possible, offloading code to other auxiliary classes. So, following some tutorials and adapting from what I have been doing until now, I was able already to set an external toolbar class that can be changed on demand, from the main app. Now, I am trying to create a class for the toolbar, but I am afraid it won't be possible to create the buttons and their respective callbacks in a separate class, as I don't know how to make them call functions that are in the main app. Is that even possible?
This is what I got right now:
#!/usr/bin/python3
from tkinter import *
from tkinter import ttk
class App:
""" main class for the application """
def __init__(self,master):
mainframe = ttk.Frame(master)
topframe = ttk.Frame(mainframe)
centerframe = ttk.Frame(mainframe)
bottomframe = ttk.Frame(mainframe)
my_toolbar = Toolbar(topframe)
my_statusbar = StatusBar(mainframe)
my_statusbar.set("This is the statusbar")
centerlabel = ttk.Label(centerframe, text="Center stuff goes here")
centerlabel.pack()
topframe.pack(side=TOP, fill=X)
centerframe.pack(side=TOP, fill=BOTH)
bottomframe.pack(side=BOTTOM, fill=X)
mainframe.pack(side=TOP, expand=True, fill=BOTH)
def button_function(self, *event):
print("filter")
class StatusBar(ttk.Frame):
""" Simple Status Bar class - based on Frame """
def __init__(self,master):
ttk.Frame.__init__(self,master)
self.label = ttk.Label(self,anchor=W)
self.label.pack()
self.pack(side=BOTTOM, fill=X)
def set(self,texto):
self.label.config(text=texto)
self.label.update_idletasks()
def clear(self):
self.label.config(text="")
self.label.update_idletasks()
class Toolbar:
""" Toolbar """
def button_one(self):
print("button 1 pressed")
def button_two(self):
print("button 2 pressed")
def __init__(self,master):
self.button1 = ttk.Button(master,text="One",command=self.button_one())
self.button2 = ttk.Button(master,text="Two",command=self.button_two())
self.button1.grid(row=0,column=0)
self.button2.grid(row=0,column=1)
if __name__ == "__main__":
root = Tk()
app = App(root)
root.mainloop()
Let's say that I need to make button1 to trigger button_function() in order to update some info being shown there. Should I simply move the toolbar into the App class, for instance in a class method called from its __init__()? Or is there a better way?
Maybe I should add that i intend later to add some Toplevelwindows that probably could make use of some of these general classes. I want to pave the road in a nice way.
This is certainly possible. There are two possibilities here. The first one to make app inherit from ttk.Frame instead of using mainframe. Then you can pass App as master to toolbar etc. Here is the redone code:
#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
class App(ttk.Frame):
""" main class for the application """
def __init__(self,master,*args,**kwargs):
super().__init__(master,*args,**kwargs)
self.my_toolbar = Toolbar(self)
self.my_statusbar = StatusBar(self)
self.my_statusbar.set("This is the statusbar")
self.centerframe = CenterFrame(self)
self.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
def button_function(self, *event):
print("filter")
class CenterFrame(ttk.Frame):
def __init__(self,master,*args,**kwargs):
super().__init__(master,*args,**kwargs)
self.master = master
self.pack(side=tk.BOTTOM, fill=tk.X)
self.centerlabel = ttk.Label(self, text="Center stuff goes here")
self.centerlabel.pack()
class StatusBar(ttk.Frame):
""" Simple Status Bar class - based on Frame """
def __init__(self,master):
ttk.Frame.__init__(self,master)
self.master = master
self.label = ttk.Label(self,anchor=tk.W)
self.label.pack()
self.pack(side=tk.BOTTOM, fill=tk.X)
def set(self,texto):
self.label.config(text=texto)
self.label.update_idletasks()
def clear(self):
self.label.config(text="")
self.label.update_idletasks()
class Toolbar(ttk.Frame):
""" Toolbar """
def button_one(self):
print("button 1 pressed")
def button_two(self):
print("button 2 pressed")
self.master.button_function()
def __init__(self,master):
super().__init__(master)
self.master = master
self.pack(side=tk.TOP, fill=tk.X)
self.button1 = ttk.Button(self,text="One",command=self.button_one)
self.button2 = ttk.Button(self,text="Two",command=self.button_two)
self.button1.grid(row=0,column=0)
self.button2.grid(row=0,column=1)
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
root.mainloop()
The second one is to just pass App as an argument to the other classes.
You are missing some self., button commands assignments don't need parenthesis and after that you can call the button configuration from anywhere in your program. So for the button1 command this will be:
app.my_toolbar.button1.config(command=app.button_function)
I am fixing your errors as is, not make the program better:
#!/usr/bin/python3
from tkinter import *
from tkinter import ttk
class App:
""" main class for the application """
def __init__(self,master):
self.mainframe = ttk.Frame(master)
self.topframe = ttk.Frame(self.mainframe)
self.centerframe = ttk.Frame(self.mainframe)
self.bottomframe = ttk.Frame(self.mainframe)
self.my_toolbar = Toolbar(self.topframe)
self.my_statusbar = StatusBar(self.mainframe)
self.my_statusbar.set("This is the statusbar")
self.centerlabel = ttk.Label(self.centerframe, text="Center stuff goes here")
self.centerlabel.pack()
self.topframe.pack(side=TOP, fill=X)
self.centerframe.pack(side=TOP, fill=BOTH)
self.bottomframe.pack(side=BOTTOM, fill=X)
self.mainframe.pack(side=TOP, expand=True, fill=BOTH)
def button_function(self, *event):
print("filter")
class StatusBar(ttk.Frame):
""" Simple Status Bar class - based on Frame """
def __init__(self,master):
ttk.Frame.__init__(self,master)
self.label = ttk.Label(self,anchor=W)
self.label.pack()
self.pack(side=BOTTOM, fill=X)
def set(self,texto):
self.label.config(text=texto)
self.label.update_idletasks()
def clear(self):
self.label.config(text="")
self.label.update_idletasks()
class Toolbar:
""" Toolbar """
def button_one(self):
print("button 1 pressed")
def button_two(self):
print("button 2 pressed")
def __init__(self,master):
self.button1 = ttk.Button(master,text="One",command=self.button_one)
self.button2 = ttk.Button(master,text="Two",command=self.button_two)
self.button1.grid(row=0,column=0)
self.button2.grid(row=0,column=1)
if __name__ == "__main__":
root = Tk()
app = App(root)
app.my_toolbar.button1.config(command=app.button_function)
root.mainloop()

How do I auto fit a Matplotlib figure inside a PySide QFrame?

I'm creating a simple PySide application that also uses MatPlotLib. However, when I add the figure into a QFrame, the figure doesn't automatically fit to the frame:
My graph is created using the following code:
class GraphView(gui.QWidget):
def __init__(self, name, title, graphTitle, parent = None):
super(GraphView, self).__init__(parent)
self.name = name
self.graphTitle = graphTitle
self.dpi = 100
self.fig = Figure((5.0, 3.0), dpi = self.dpi, facecolor = (1,1,1), edgecolor = (0,0,0))
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.Title = gui.QLabel(self)
self.Title.setText(title)
self.layout = gui.QVBoxLayout()
self.layout.addStretch(1)
self.layout.addWidget(self.Title)
self.layout.addWidget(self.canvas)
self.setLayout(self.layout)
def UpdateGraph(self, data, title = None):
self.axes.clear()
self.axes.plot(data)
if title != None:
self.axes.set_title(title)
self.canvas.draw()
And it's added to the main Widget like so:
# Create individual Widget/Frame (fftFrame)
fftFrame = gui.QFrame(self)
fftFrame.setFrameShape(gui.QFrame.StyledPanel)
self.FFTGraph = GraphView('fftFrame', 'FFT Transform:', 'FFT Transform of Signal', fftFrame)
Here's a working code sample that shows you how to get it working. I first thought it was because of the stretch you added to the layout, which will use up the additional space around the other widgets. But when I removed it, it still wouldn't resize. The 'easy' solution is to add a resizeEvent, which lets you define the size of your GraphView widget. In this case I just set its geometry to be that of the QFrame, though you might want to add some padding and make sure you set a sensible minimum size for the QFrame.
from PySide import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.fft_frame = FftFrame(self)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.fft_frame)
self.setLayout(self.layout)
self.setCentralWidget(self.fft_frame)
class FftFrame(QtGui.QFrame):
def __init__(self, parent=None):
super(FftFrame, self).__init__(parent)
self.setFrameShape(QtGui.QFrame.StyledPanel)
self.parent = parent
self.graph_view = GraphView('fftFrame', 'FFT Transform:', 'FFT Transform of Signal', self)
def resizeEvent(self, event):
self.graph_view.setGeometry(self.rect())
class GraphView(QtGui.QWidget):
def __init__(self, name, title, graph_title, parent = None):
super(GraphView, self).__init__(parent)
self.name = name
self.graph_title = graph_title
self.dpi = 100
self.fig = Figure((5.0, 3.0), dpi = self.dpi, facecolor = (1,1,1), edgecolor = (0,0,0))
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self)
self.Title = QtGui.QLabel(self)
self.Title.setText(title)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.Title)
self.layout.addWidget(self.canvas)
self.layout.setStretchFactor(self.canvas, 1)
self.setLayout(self.layout)
self.canvas.show()
def update_graph(self, data, title = None):
self.axes.clear()
self.axes.plot(data)
if title != None:
self.axes.set_title(title)
self.canvas.draw()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

Resources