I am trying to build a simple GUI using tkinter to read in CSV's and then run some text mining functions. I am having difficulty understand tkinter's callback function. The following code was created using this ToyMVC code.
I am able to get the GUI to pop up but am having trouble getting the rightmost Entry widget (variable name = finalPathEntry) to update with the user-inputted text from the left Widget once the user presses the button.
I would greatly appreciate any advice on how to move forward and what I am doing wrong.
Best,
import tkinter as tk
class Observable:
def __init__(self, initialValue=None):
self.data = initialValue
self.callbacks = {}
def addCallback(self, func):
self.callbacks[func] = 1
def delCallback(self, func):
del self.callback[func]
def _docallbacks(self):
for func in self.callbacks:
func(self.data)
def set(self, data):
self.data = data
self._docallbacks()
def get(self):
return self.data
def unset(self):
self.data = None
class Model:
def __init__(self):
self.csvPath = Observable("")
def addPath(self, value):
self.csvPath.set(self.csvPath.get())
class View(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self, master)
self.protocol('WM_DELETE_WINDOW', self.master.destroy)
tk.Label(self, text='Please input path to CSV').pack(side='left')
self.pathEntry = tk.Entry(self, width=50)
self.pathEntry.pack(side='left')
self.addButton = tk.Button(self, text='Read CSV', width=8)
self.addButton.pack(side='left')
self.path = tk.StringVar()
self.finalPathEntry = tk.Entry(self, width = 30, textvariable = self.path)
self.finalPathEntry.pack(side='left')
def setPath(self, value):
self.path.set(str(value))
class Controller:
def __init__(self, root):
self.model = Model()
self.model.csvPath.addCallback(self.pathChanged)
self.view1 = View(root)
self.view1.minsize(width = 500, height = 500)
self.view1.addButton.config(command=self.addPath)
self.pathChanged(self.model.csvPath.get())
def addPath(self):
self.model.addPath(self.view1.pathEntry.get())
def pathChanged(self, val):
self.view1.setPath(val)
if __name__ == '__main__':
root = tk.Tk()
root.withdraw()
app = Controller(root)
root.mainloop()
EDIT: Using the answer below, I have fixed my code. Here is the updated and working snippet:
import tkinter as tk
class Observable:
def __init__(self, initialValue = None):
self.data = initialValue
self.callbacks = {}
def addCallback(self, func):
self.callbacks[func] = 1
def delCallback(self, func):
del self.callback[func]
def _docallbacks(self):
for func in self.callbacks:
func(self.data)
def set(self, data):
self.data = data
self._docallbacks()
def get(self):
return self.data
def unset(self):
self.data = None
class Model:
def __init__(self):
self.csvPath = Observable("")
def addPath(self, value):
self.csvPath.set(self.csvPath.get())
class View(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self, master)
self.protocol('WM_DELETE_WINDOW', self.master.destroy)
tk.Label(self, text='Please input path to CSV').pack(side='left')
self.pathEntry = tk.Entry(self, width=50)
self.pathEntry.pack(side='left')
self.addButton = tk.Button(self, text='Read CSV', width=8)
self.addButton.pack(side='left')
self.path = tk.StringVar()
self.finalPathEntry = tk.Entry(self, width = 30, textvariable = self.path)
self.finalPathEntry.pack(side='left')
def setPath(self, value):
self.path.set(str(value))
class Controller:
def __init__(self, root):
self.model = Model()
self.model.csvPath.addCallback(self.pathChanged)
self.view1 = View(root)
self.view1.minsize(width = 500, height = 500)
self.view1.addButton.config(command = self.addPath)
self.pathChanged(self.model.csvPath.get())
def addPath(self):
self.model.addPath(self.view1.pathEntry.get())
def pathChanged(self, val):
self.view1.setPath(val)
if __name__ == '__main__':
root = tk.Tk()
root.withdraw()
app = Controller(root)
root.mainloop()
One thing about this code seems obvious: The functions beginning at
def addcallback(self, func):
until
def unset(self):
are not part of the Observable class. So the "self" argument is not what you expect it to be. You should indent these functions.
Related
I am trying to pass data between two wxpython windows using PyPubSub. Below I have pared the code down the the basics. What I am getting when I fill in the data in the textcntrl on the main window and hit the button to pass it to the second window (CoordFrame) I am getting the following error-
TypeError: sendMessage() takes 2 positional arguments but 3 were given
Every example I can find shows passing two parameters in the pub.sendMessage; which is what I think I'm doing. What is the 3rd parameter being passed, where does it come form and how do I stop it? I have tried various combinations of arguments, and datatypes including binary.
'''
from pubsub import pub
import wx
class MainFrame (wx.Frame):
def __init__(self, parent, title):
super(MainFrame, self).__init__(parent, title = title, size = (200,200))
self.panel = MainPanel(self)
class CoordFrame (wx.Frame):
def __init__(self, parent, title):
super(CoordFrame, self).__init__(parent, title = title, size = (200,200))
self.panel = CoordPanel(self)
class MainPanel(wx.Panel):
def __init__(self, parent):
super(MainPanel, self).__init__(parent)
vsizer = wx.BoxSizer(wx.VERTICAL)
self.tbxNewMap = wx.TextCtrl(self, id=1001, pos=(20,20), size = (50,20) ,style = wx.TE_CENTER|wx.TE_NOHIDESEL|wx.TE_PROCESS_ENTER)
vsizer.Add(self.tbxNewMap)
self.btnEnterNewMap = wx.Button(self, id=1002, label = "New Data", pos = (20,80), size = (80,40))
vsizer.Add(self.btnEnterNewMap,0,wx.EXPAND)
self.Bind(wx.EVT_BUTTON, self.onButtonNewMap, id=1002)
def onButtonNewMap(self,event):
temp = self.tbxNewMap.GetValue()
pub.sendMessage("coord_listener", temp)
coordframe = CoordFrame(None,"Entry")
coordframe.Show()
class CoordPanel(wx.Panel):
def __init__(self, parent):
super(CoordPanel, self).__init__(parent)
vsizer = wx.BoxSizer(wx.VERTICAL)
pub.subscribe(self.coord_listener, "coord_listener")
self.tbxNewMapNumber = wx.TextCtrl(self, id=1000, pos=(20,20), size = (50,20), style = wx.TE_CENTER|wx.TE_NOHIDESEL|wx.TE_PROCESS_ENTER)
vsizer.Add(self.tbxNewMapNumber)
def coord_listener(self, message):
newmapnum = message
self.tbxNewMapNumber.SetValue(newmapnum)
self.tbxNewMapNumber.Refresh()
class GMDash(wx.App):
def OnInit(self):
self.mainframe = MainFrame(parent = None, title = "Dashboard")
self.mainframe.Show()
return True
app = GMDash()
app.MainLoop()
'''
Use a named argument in the call to sendmessage and you'll need to register the listener before sending the message, not after, as you are currently doing.
See below:
from pubsub import pub
import wx
class MainFrame (wx.Frame):
def __init__(self, parent, title):
super(MainFrame, self).__init__(parent, title = title, size = (200,200))
self.panel = MainPanel(self)
class CoordFrame (wx.Frame):
def __init__(self, parent, title):
super(CoordFrame, self).__init__(parent, title = title, size = (200,200))
self.panel = CoordPanel(self)
class MainPanel(wx.Panel):
def __init__(self, parent):
super(MainPanel, self).__init__(parent)
vsizer = wx.BoxSizer(wx.VERTICAL)
self.tbxNewMap = wx.TextCtrl(self, id=1001, pos=(20,20), size = (50,20) ,style = wx.TE_CENTER|wx.TE_NOHIDESEL|wx.TE_PROCESS_ENTER)
vsizer.Add(self.tbxNewMap)
self.btnEnterNewMap = wx.Button(self, id=1002, label = "New Data", pos = (20,80), size = (80,40))
vsizer.Add(self.btnEnterNewMap,0,wx.EXPAND)
self.Bind(wx.EVT_BUTTON, self.onButtonNewMap, id=1002)
#Register the subscrption *before* sending the message
self.coordframe = CoordFrame(None,"Entry")
def onButtonNewMap(self,event):
temp = self.tbxNewMap.GetValue()
pub.sendMessage("coord_listener", message=temp)
#coordframe = CoordFrame(None,"Entry")
self.coordframe.Show()
class CoordPanel(wx.Panel):
def __init__(self, parent):
super(CoordPanel, self).__init__(parent)
vsizer = wx.BoxSizer(wx.VERTICAL)
pub.subscribe(self.coord_listener, "coord_listener")
self.tbxNewMapNumber = wx.TextCtrl(self, id=1000, pos=(20,20), size = (50,20), style = wx.TE_CENTER|wx.TE_NOHIDESEL|wx.TE_PROCESS_ENTER)
vsizer.Add(self.tbxNewMapNumber)
def coord_listener(self, message):
print(message)
newmapnum = message
self.tbxNewMapNumber.SetValue(newmapnum)
self.tbxNewMapNumber.Refresh()
class GMDash(wx.App):
def OnInit(self):
self.mainframe = MainFrame(parent = None, title = "Dashboard")
self.mainframe.Show()
return True
app = GMDash()
app.MainLoop()
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super(MyFrame, self).__init__(parent, title=title, size=(620, 665))
splitter = wx.SplitterWindow(self, wx.ID_ANY)
self.MainPanel = NewPanel(splitter)
self.MyNotebook = Nbook(splitter)
splitter.SplitHorizontally(self.MainPanel, self.MyNotebook, sashPosition=210)
self.sb = self.CreateStatusBar(3)
self.MainPanel.Show()
self.Centre()
def setNotebookDisabled(self):
self.MyNotebook.Enabled = False
def PrintSomething(self):
print("Something")
class NewPanel(wx.Panel):
def __init__(self, parent):
super(NewPanel, self).__init__(parent=parent)
def Disable_Controls(self):
self.GrandParent.MyNotebook.Enabled = False
class Nbook(wx.Notebook):
def __init__(self, parent):
super(Nbook, self).__init__(parent)
# self.MyNotebook = wx.Notebook(splitter,wx.ID_ANY)
page_one = NbPanel1(self)
page_two = NbPanel2(self)
page_three = NbPanel3(self)
self.AddPage(page_one, "Cable")
self.AddPage(page_two, "Busduct")
self.AddPage(page_three, "Transformer")
I can call PrintSomething() with success however...
The error:
File "c:/Users/Mark/Documents/FaultWireCalc/FCCalc.py", line 331, in Disable_Controls
self.GrandParent.MyNotebook.Enabled = False
AttributeError: 'MyFrame' object has no attribute 'MyNotebook'
This is not so much an answer as a work in progress, given that a comment simply wouldn't hack it.
Given that you haven't stated when or how the error occurs and it's occurring somewhere in 1000 lines of code, which we can't see, right about now might be a good time for
a minimal, complete and verifiable example (mcve)
https://stackoverflow.com/help/minimal-reproducible-example
As you seem reluctant to provide one, let's try this:
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super(MyFrame, self).__init__(parent, title=title, size=(620, 665))
splitter = wx.SplitterWindow(self, wx.ID_ANY)
self.MainPanel = NewPanel(splitter)
self.MyNotebook = Nbook(splitter)
splitter.SplitHorizontally(self.MainPanel, self.MyNotebook, sashPosition=210)
self.sb = self.CreateStatusBar(3)
self.MainPanel.Show()
self.Centre()
self.Show()
def setNotebookDisabled(self):
self.MyNotebook.Enabled = False
def PrintSomething(self):
print("Something")
class NewPanel(wx.Panel):
def __init__(self, parent):
super(NewPanel, self).__init__(parent=parent)
testbut = wx.Button(self, -1, "Panel Hit Me")
self.Bind(wx.EVT_BUTTON, self.Disable_Controls)
def Disable_Controls(self, event):
self.GrandParent.MyNotebook.Enabled = not self.GrandParent.MyNotebook.IsEnabled()
class Nbook(wx.Notebook):
def __init__(self, parent):
super(Nbook, self).__init__(parent)
# self.MyNotebook = wx.Notebook(splitter,wx.ID_ANY)
page_one = wx.Panel(self)
page_two = wx.Panel(self)
page_three = wx.Panel(self)
self.AddPage(page_one, "Cable")
self.AddPage(page_two, "Busduct")
self.AddPage(page_three, "Transformer")
if __name__ == "__main__":
app = wx.App()
frame = MyFrame(None, "Test")
app.MainLoop()
As you can see, it does function (below) the notebook can be enabled/disabled.
So, can you make this produce the error or deduce from it, how your code differs in the way or when the code is called, that produces the error?
You may find that GrandParent is not what you think it is! (I suspect splitter is involved)
You might try:
print("I am",self)
print("My parent is",self.Parent)
print("Granddad is",self.GrandParent)
print("His parent is",self.GrandParent.GetParent())
The other thing you might try when calling the routine, is to call it directly:
MyFrame.setNotebookDisabled(parent.GetParent())
or a variation to get the right parent, depending on where you are calling it from.
Over to you.
I am trying to create couple of windows classed using Tkinter. I get no error when I run the code but it doesn't display anything. Am I missing anything?
def main():
root = Tk()
app = startpage(root)
class startpage:
def __init__(self, master):
self.master = master
self.master.title("Main Page")
self.master.geometry('1350x750+0+0')
self.master.config(bg = "blue")
self.frame = Frame(self.master, bg = 'blue')
self.frame.pack()
def EntryWin(self):
self.Entry_win = Toplevel(self.master)
self.app = Entrypage(self.Entry_win)
class Entrypage:
def __init__(self, master):
self.master = master
self.master.title("Entry Page")
self.master.geometry('1350x750+0+0')
self.master.config(bg = "green")
self.frame = Frame(self.master, bg = 'blue')
self.frame.pack()
if __name__ == 'main__':
main()
You have misspelt __main__ as main__ when you call the main function.
Also import tkinter at the beginning of the code, and add root.mainloop() in the main().
from tkinter import *
Here is the corrected code:
from tkinter import *
def main():
root = Tk()
app = startpage(root)
root.mainloop()
class startpage:
def __init__(self, master):
self.master = master
self.master.title("Main Page")
self.master.geometry('1350x750+0+0')
self.master.config(bg = "blue")
self.frame = Frame(self.master, bg = 'blue')
self.frame.pack()
def EntryWin(self):
self.Entry_win = Toplevel(self.master)
self.app = Entrypage(self.Entry_win)
self.master.mainloop()
class Entrypage:
def __init__(self, master):
self.master = master
self.master.title("Entry Page")
self.master.geometry('1350x750+0+0')
self.master.config(bg = "green")
self.frame = Frame(self.master, bg = 'blue')
self.frame.pack()
self.master.mainloop()
if __name__ == '__main__':
main()
The error message when I hit the "forward" button is "list index out of range".
Hi I have been working on this problem for a few weeks now. With my previous post, someone suggested that I learn how to use lambda function properly, which I did. I worked on a few simpler version to see if I understand the concept and now I come to this version.
The way the code is set up, everything will run properly other than 1 small error. I can hit the "calculate" button on each notebook, and it will run the label and the model result from Pulp. I can even hit the "forward" button and it will calculate with Pulp all the notebooks' printed result, but it will not display the "week 2"'s label.
I don't know where is the problem, or what code that I am missing, or code that i need edit with.
If someone can assist me, that would be great so I can get over this hump and use the same concept with my larger program.
import tkinter as tk
from tkinter import ttk
from functools import partial
import random
import pulp
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("notbook my own try")
self.geometry("550x350")
self.config(bg="tan")
self.style = ttk.Style()
self.lnb = Sheets(self)
class Sheets(ttk.Notebook):
def __init__(self, parent):
parent.style.configure("down.TNotebook", tabposition="sw")
super().__init__(parent, style = "down.TNotebook")
self.pack(fill = tk.BOTH, expand =1)
self.parent = parent
self.week_index = ['Week 1', "Week 2"]
self.sheet_dict = {}
for num, week in enumerate(self.week_index, 1 ):
self.week = One_Weekly_Notebook(self, num)
self.week.pack()
self.add(self.week, text = week)
self.pack()
self.sheet_dict[week] = self.week
class One_Weekly_Notebook(ttk.Notebook):
def __init__(self, parent, index):
super().__init__(parent)
self.pack(fill = tk.BOTH, expand =1)
self.index = index
self.parent = parent
self.upper_tabs_dict = {}
self.labelVariable_list = []
self.label_list = []
self.Var_list = []
self.dropMenu_list = []
self.answer_list = []
self.model_list = []
self.optionList=["1", "2", "3", "4", "5", "6"]
self.optionList_random = []
self.object_final = Final(self)
tab1 = self.object_final
tab1.pack()
self.add(tab1, text = "Final")
self.pack()
self.upper_tabs_dict["Final"] = tab1
def pulp_process(self, button):
self.a_value = int(self.parent.sheet_dict[f'Week
{button}'].Var_list[0].get()) #1st dropdown value
self.b_value= int(self.parent.sheet_dict[f'Week
{button}'].Var_list[1].get()) #2nd dropdown value
# Instantiate our problem class
self.model = pulp.LpProblem("Profit maximising problem",
pulp.LpMaximize)
self.A = pulp.LpVariable('A', lowBound=0, cat='Integer')
self.B = pulp.LpVariable('B', lowBound=0, cat='Integer')
# Objective function
self.model += (self.a_value * self.A ) + (self.b_value *
self.B), "Profit"
# Constraints
self.model += 3 * self.A + 4 * self.B <= 3
self.model += 5 * self.A + 6 * self.B <= 6
self.model += 1.5 * self.A + 3 * self.B <= 2
# Solve the problem
self.model.solve()
self.answer= pulp.value(self.model.objective)
def see_label(self, button):
self.parent.sheet_dict[f"Week
{button}"].label_list[0].configure
(text = self.parent.sheet_dict[
f"Week {button}"].answer)
class Final(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.pack(fill=tk.BOTH, expand=1)
self.parent = parent
self.labels()
def labels(self):
self.label=tk.Label(self, bg="orange", fg="white")
self.label.pack()
self.parent.label_list.append(self.label)
self.random()
def random(self):
for i in range(2):
self.random = random.choice(self.parent.optionList)
self.parent.optionList_random.append(self.random)
self.dropdowns()
def dropdowns(self):
for i in range(2):
self.Var = tk.StringVar()
self.dropMenu=tk.OptionMenu(self, self.Var,
*self.parent.optionList)
self.dropMenu.config(width = 10)
self.dropMenu.pack(side = "left")
self.parent.Var_list.append(self.Var)
self.parent.dropMenu_list.append(self.dropMenu)
self.parent.Var_list[i].set
(self.parent.optionList_random[i])
self.buttons()
def buttons(self):
value = self.parent.index
self.button=tk.Button(self, text="calculate")
self.button.pack(side = "left")
self.button.configure(command= lambda button=value:
self.parent.pulp_process(button))
self.button = tk.Button(self, text="Forward", command =
self.forward)
self.button.pack ()
def forward(self):
self.parent.pulp_process(1)
self.parent.pulp_process(2)
if __name__ == '__main__':
Application().mainloop()
When I hit the "forward" button, it should show the pulp results onto the label in all the weeks together.
I am trying to create a GUI using the first answer here. I am running into some issues because I don't fully understand how everything should be connected.
I am using a class to create a grid of boxes. Each box uses bind to call some function when clicked.
Where do I create the Many_Boxes class? see all the way at the bottom for an example of what I'm trying to do.
In the Many_Boxes class that is in the Main class I have a actions that I bind to a function. Where do I put that function? How do I call that function? What if I want to call that function from the Nav class?
I have:
class Nav(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
class Main(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.hand_grid_dict = self.create_hand_grid()
class Many_Boxes:
<<<<<<<<< bunch of code here >>>>>>>>>>>>>>
self.hand_canvas.bind("<Button-1>", lambda event: button_action(canvas_hand)) <<<<<< WHAT DO I NAME THIS??? self.parent.....?
class MainApplication(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.navbar = Nav(self)
self.main = Main(self)
self.navbar.pack(side="left", fill="y")
self.main.pack(side="right", fill="both", expand=True)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).grid(row=1, column=1)
root.mainloop()
Where do I put this:
def create_grid(self):
for x in y:
your_box = self.Many_Boxes(.......)
If I'm understanding your question correctly, there is no need to create a class just to create the boxes. All you have to do is replace the Many_Boxes class with your create_hand_grid function, and define the button_action function:
import tkinter as tk
class Navbar(tk.Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
tk.Label(self.parent, text='Navbar').pack()
tk.Button(self.parent, text='Change Color', command=self.change_color).pack()
def change_color(self):
# access upwards to MainApp, then down through Main, then ManyBoxes
self.parent.main.many_boxes.boxes[0].config(bg='black')
class ManyBoxes(tk.Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.boxes = []
self.create_boxes()
def button_action(self, e):
print('%s was clicked' % e.widget['bg'])
def create_boxes(self):
colors = ['red', 'green', 'blue', 'yellow']
c = 0
for n in range(2):
for m in range(2):
box = tk.Frame(self, width=100, height=100, bg=colors[c])
box.bind('<Button-1>', self.button_action)
box.grid(row=n, column=m)
self.boxes.append(box)
c += 1
class Main(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.many_boxes = ManyBoxes(self)
self.many_boxes.pack()
class MainApp(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.navbar = Navbar(self)
self.navbar.pack(fill=tk.Y)
self.main = Main(self)
self.main.pack(fill=tk.BOTH, expand=True)
if __name__ == "__main__":
root = tk.Tk()
MainApp(root).pack()
root.mainloop()
I've filled in create_hand_grid and button_action so you can copy-paste the code and see it work.