How to transfer data between Toplevel widgets (OOP): - python-3.x

I am beginner to Python language !
I designed three windows by three classes.
First window is the main window and it is on the first class.
And the others defined as toplevel widgets by another two classes.
I need to send data which input to the second window (first toplevel widget) and get (view) it from the third window (second toplevel widget).
So, to do that I used the - getattr - function.
But it gaven some (AttributeError) error :
d = self.var1.get()
File "C:\Python\Python310\lib\tkinter_init_.py", line 2383, in getattr
return getattr(self.tk, attr)
AttributeError: '_tkinter.tkapp' object has no attribute 'var1'
from tkinter import *
import tkinter as tk
class obj_1(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("TK WINDOW 01")
self.geometry("300x150+10+10")
btn_next = tk.Button(self, text="Second Window", width=15, height=1, command=lambda: obj_2())
btn_next.pack(pady=20)
class obj_2(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(self, *args, **kwargs)
self.title("TK WINDOW 02")
self.geometry("400x100+300+300")
self.var1 = tk.StringVar()
btn_next = tk.Button(self, text="-->", width=15, height=1, command=lambda: obj_3())
btn_next.pack()
self.lbl_ent_1 = tk.Entry(self, textvariable=self.var1, width=15)
self.lbl_ent_1.pack(pady=20)
self.lbl_ent_1.focus()
def get_from_second(self):
d = self.var1.get()
return d
class obj_3(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(self, *args, **kwargs)
self.title("TK WINDOW 03")
self.geometry("400x100+800+300")
self.var2 = tk.StringVar()
btn_view = tk.Button(self, text="View", width=15, height=1, command=lambda: self.get_from_third())
btn_view.pack()
self.lbl_vw = tk.Label(self, textvariable=self.var2, width=15, height=1, pady=20)
self.lbl_vw.pack()
def get_from_third(self):
p = getattr(obj_2, 'get_from_second')(self)
self.var2.set(p)
print(p)
if __name__ == '__main__':
app = obj_1()
app.mainloop()

I commented the changes where they were made, but in short: you had the wrong super for both of your toplevel windows, with the proper super you get a master to refer to. Too much juggling (C from A from B). My example is direct (B from A) then (C from A). You don't even need the get_from_second because it has been obsoleted by a virtual get_directly_from_root. In other words, store your var on the root and then refer to it via master.
from tkinter import *
import tkinter as tk
class obj_1(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("TK WINDOW 01")
self.geometry("300x150+10+10")
btn_next = tk.Button(self, text="Second Window", width=15, height=1, command=lambda: obj_2(self))
btn_next.pack(pady=20)
self.var1 = tk.StringVar() #store the var on the master
class obj_2(tk.Toplevel): #Toplevel not Tk
def __init__(self, master, *args, **kwargs): #master will be your main Tk window
tk.Toplevel.__init__(self, master, *args, **kwargs)
self.title("TK WINDOW 02")
self.geometry("400x100+300+300")
btn_next = tk.Button(self, text="-->", width=15, height=1, command=lambda: obj_3(master))
btn_next.pack()
self.lbl_ent_1 = tk.Entry(self, textvariable=master.var1, width=15) #<--var from master
self.lbl_ent_1.pack(pady=20)
self.lbl_ent_1.focus()
class obj_3(tk.Toplevel): #Toplevel not Tk
def __init__(self, master, *args, **kwargs): #master will be your main Tk window
tk.Toplevel.__init__(self, master, *args, **kwargs)
self.title("TK WINDOW 03")
self.geometry("400x100+800+300")
self.var2 = tk.StringVar()
btn_view = tk.Button(self, text="View", width=15, height=1, command=lambda: self.get_from_third())
btn_view.pack()
self.lbl_vw = tk.Label(self, textvariable=self.var2, width=15, height=1, pady=20)
self.lbl_vw.pack()
def get_from_third(self):
self.var2.set(self.master.var1.get())
if __name__ == '__main__':
app = obj_1()
app.mainloop()
I left your code mostly in tact but consider the below. Your button is entirely unnecessary, you can just automatically have the data by sharing var1 from master.
class obj_3(tk.Toplevel): #Toplevel not Tk
def __init__(self, master, *args, **kwargs): #master will be your main Tk window
tk.Toplevel.__init__(self, master, *args, **kwargs)
self.title("TK WINDOW 03")
self.geometry("400x100+800+300")
self.lbl_vw = tk.Label(self, textvariable=master.var1, width=15, height=1, pady=20)
self.lbl_vw.pack()
Aside: All this obj_x naming is very poor practice. Give your classes meaningful names that give an indication of what they do. The same goes for varx, and any other name you make which is entirely arbitrary. Imagine if tkinter named everything Widget1, Widget2, etc...How could you possibly work with that? The same goes for your code. You keep naming everything arbitrarily and you wont be able to work with your own code.
What is vw in self.lbl_vw? VariableWindow, perhaps? That sounds like a great name for that class. While you're at it, stop naming your classes with definition syntax. Even if you kept the poor obj_x naming it should still, at least, be: Obj_x.
Not really a question:
Why are you opening a 3rd window to display a variable that was just typed, and why would you have to click a button to see it? Again, I'm not really asking this. I want you to ask yourself that. Now that I've boiled your code down to one var, your app doesn't make any sense in design. Your app is currently 3 entire windows built around 1 variable. It's probably time to reconsider what it is you are actually trying to do.
Imagine if you opened your browser, had to click a search button which opens up a query window, and then click another button which opens ANOTHER window with the page in it. It looks like you are essentially building that. It's a bad design.

Related

How can I set the default container in a decorator class for tkinter.Frame?

I would like to create a contractible panel in a GUI, using the Python package tkinter.
My idea is to create a decorator for the tkinter.Frameclass, adding a nested frame and a "vertical button" which toggles the nested frame.
Sketch: (Edit: The gray box should say Parent of contractible panel)
I got it to toggle just fine, using the nested frame's grid_remove to hide it and then move the button to the left column (otherwise occupied by the frame).
Now I want to be able to use it like any other tkinter.Frame, but let it target the nested frame. Almost acting like a proxy for the nested frame. For example, adding a tkinter.Label (the green Child component in the sketch) to the decorator should add the label to the nested frame component (light yellow tk.Frame in the sketch) not the decorator itself (strong yellow ContractiblePanel in the sketch).
Minimal example: (omitting the toggling stuff and any "formatting"):
(Here's a published (runnable) Repl project)
import tkinter
class ContractiblePanel(tkinter.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self._panel = tkinter.Frame(self)
self._toggle = tkinter.Button(self, text='<', command=self._toggle_panel)
self.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
def _toggle_panel(self):
# ...
if __name__ == '__main__':
root = tkinter.Tk()
root.geometry('128x128')
contractible_panel = ContractiblePanel(root)
Forwarding configuration calls is just overriding the config method I guess?
class ContractiblePanel(tkinter.Frame):
# ...
def config(self, **kwargs):
self._panel.config(**kwargs)
# ...
contractible_panel.config(background='blue')
But I would like to be able to add a child component into the nested panel frame by
label_in_panel = tkinter.Label(contractible_panel, text='yadayada')
How do I get the ContractiblePanel object to act like a proxy to its member _panel, when adding child components?
What other methods/use cases should I consider? I am quite new to tkinter and thus expect the current implementation to break some common practices when developing tkinter GUIs.
This is an interesting question. Unfortunately, tkinter really isn't designed to support what you want. I think it would be less complicated to simply expose the inner frame and add widgets to it.
That being said, I'll present one possible solution. It's not implemented as a python decorator, but rather a custom class.
The difficulty is that you want the instance of the custom class to represent the outer frame in one context (for example, when packing it in your UI) and the inner frame in another context (when adding child widgets to it)
The following solution solves this by making the instance be the inner frame, and then overriding pack,place, and grid so that they operates on the outer frame. This works fine, with an important exception: you cannot use this class directly inside a notebook or embedded in a text widget or canvas.
I've used colors and borders so it's easy to see the individual components, but you can remove the colors in production code, obviously. Also, I used a label instead of a button since I created the screenshot on OSX where the background color of a button can't be changed.
import tkinter as tk
class ContractiblePanel(tk.Frame):
def __init__(self, parent, **kwargs):
self._frame = tk.Frame(parent, **kwargs)
super().__init__(self._frame, bd=2, relief="solid", bg="#EFE4B0")
self._button = tk.Label(
self._frame, text="<", bg="#00A2E8", bd=2,
relief="solid", font=("Helvetica", 20), width=4
)
self._frame.grid_rowconfigure(0, weight=1)
self._frame.grid_columnconfigure(0, weight=1)
self._button.grid(row=0, column=1, sticky="ns", padx=4, pady=4)
super().grid(row=0, column=0, sticky="nsew", padx=4, pady=4)
self._button.bind("<1>", lambda event: self.toggle())
def collapse(self):
super().grid_remove()
self._button.configure(text=">")
def expand(self):
super().grid()
self._button.configure(text="<")
def toggle(self):
self.collapse() if self.winfo_viewable() else self.expand()
def pack(self, **kwargs):
# override to call pack in the private frame
self._frame.pack(**kwargs)
def grid(self, **kwargs):
# override to call grid in the private frame
self._frame.grid(**kwargs)
def place(self, **kwargs):
# override to call place in the private frame
self._frame.place(**kwargs)
root = tk.Tk()
root.geometry("400x300")
cp = ContractiblePanel(root, bg="yellow", bd=2, relief="raised")
cp.pack(side="left", fill="y", padx=10, pady=10)
label = tk.Label(cp, text="Child component", background="#22B14C", height=3, bd=2, relief="solid")
label.pack(side="top", expand=True, padx=20, pady=20)
root.mainloop()
First of all it is kinda gross to use this code and it's very confusing. So I'm really not sure if you really want to take this route. However, it is possible to achieve it.
The basic idea is to have a wrapper and to pretend the wrapper is the actual object you can lie with __str__ and __repr__ about what the class really is. That is not what a proxy means.
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
You can do even more confusing things by delegate the lookup-chain to the _wrapped_frame inside the WrapperClass this enables you to call on the instance of WrapperFrame() methods like pack or every other method. It kinda works similar for inheritance with the difference that by referring to the object, you will point to different one.
I don't recommend using this code by the way.
import tkinter as tk
NONE = object()
#use an object here that there will no mistake
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
def __getattr__(self, name):
#when wrapper class has no attr name
#delegate the lookup chain to self.frame
inreturn = getattr(self._wrapped_frame, name, NONE)
if inreturn is NONE:
super().__getattribute__(name)
return inreturn
root = tk.Tk()
wrapped_frame = WrapperClass(root, bg='red', width=200, height=200)
root.mainloop()

Trouble Binding a Button in a Class - Python (Tkinter)

Just started learning Tkinter and was hoping someone could help me. I've been trying to bind a keyboard character (Enter button) to a tk button following this example and not getting anywhere.
Say I take the button (Enter) and try bind it nothing happens:
Enter.bind('<Return>', lambda:self.retrieve_Input(t))
If I bind to self instead using Lambda nothing happens also. I can get it to trigger if I remove the lambda but that's not the desired outcome
self.bind('<Return>', lambda:self.retrieve_Input(t))
My Code:
import sys
import tkinter as tk
from tkinter import ttk
class windows(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.wm_title("Test Application")
self.lift() #Bringing the GUI to the front of the screen
main_frame = tk.Frame(self, height=400, width=600) #Creating a main Frame for all pages
main_frame.pack(side="top", fill="both", expand=True)
main_frame.grid_rowconfigure(0, weight=1) #Configuring the location of the main frame using grid
main_frame.grid_columnconfigure(0, weight=1)
# We will now create a dictionary of frames
self.frames = {}
for F in (MainPage, CompletionScreen): #Add the page components to the dictionary.
page = F(main_frame, self)
self.frames[F] = page #The windows class acts as the root window for the frames.
page.grid(row=0, column=0, sticky="nsew")
self.show_page(MainPage) #Method to switch Pages
def show_page(self, cont):
frame = self.frames[cont]
frame.tkraise()
##########################################################################
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#switch_window_button = tk.Button(self, text="Go to the Side Page", command=lambda: controller.show_page(SidePage))
#switch_window_button.pack(side="bottom", fill=tk.X)
tk.Label(self, text="Project Python Search Engine", bg='white').pack()
tk.Label(self, text="", bg='white').pack()
tk.Label(self, text="Song", bg='white').pack()
tk.Label(self, text="", bg='white').pack()
t = tk.Entry(self, bg='white', width = 50)
t.pack()
tk.Label(self, text="", bg='white').pack()
Enter = tk.Button(self, text='Search', command= lambda:self.retrieve_Input(t))
Enter.pack()
tk.Button(self, text="Latest Popular Songs", command=lambda:self.Popular_Songs(t)).pack() #Line 210 onwards
Enter.bind('<Return>', lambda:self.retrieve_Input(t))
def retrieve_Input(self, t):
print ("work")
print (t)
class CompletionScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Completion Screen, we did it!")
label.pack(padx=10, pady=10)
switch_window_button = ttk.Button(
self, text="Return to menu", command=lambda: controller.show_page(MainPage)
)
switch_window_button.pack(side="bottom", fill=tk.X)
if __name__ == "__main__":
App = windows()
App.mainloop()
I'm not really sure what I'm missing
Answer: The button probably doesn't have the keyboard focus. When I run your code and then use the keyboard to move the focus to the button, your binding works. You probably want to bind to the entry widget rather than the button since that's what will have the keyboard focus. – Thanks Bryan Oakley

Tkinter Frozen Screen when start button is clicked

I'm trying to write a tkinter program with start and stop buttons. For both the start and stop buttons, I am importing two different functions from different .py files. When I click on the start button, tkinter freezes and my cursor is constantly "processing" and does not let me click on the stop button.
My tkinter program is below:
import tkinter as tk
from start import hello
from stop import quitprog
class Page(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
def show(self):
self.lift()
class Page1(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs)
label = tk.Label(self, text="Collecting Data Now...")
label.pack(side="top", fill="both", expand=True)
class Page2(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs)
label = tk.Label(self, text="Analyzing Data Now...")
label.pack(side="top", fill="both", expand=True)
class MainView(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
p1 = Page1(self)
p2 = Page2(self)
buttonframe = tk.Frame(self)
container = tk.Frame(self)
buttonframe.pack(side="top", fill="x", expand=False)
container.pack(side="top", fill="both", expand=True)
p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p2.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
b1 = tk.Button(buttonframe, text="start", command=hello)
b2 = tk.Button(buttonframe, text="stop", command=quitprog)
b1.pack(side="left")
b2.pack(side="left")
p1.show()
if __name__ == "__main__":
root = tk.Tk()
main = MainView(root)
main.pack(side="top", fill="both", expand=True)
root.wm_geometry("400x400")
root.mainloop()
My start.py is below:
import time
def hello():
for i in range(20):
print("hello world")
time.sleep(1)
My stop.py:
import sys
def quitprog():
sys.exit()
My frozen window:
loading/processing cursor image
Please let me know how to fix this.
Edit: Instead of the start.py program, I'm actually invoking a live twitter stream and that doesn't make use of .sleep(). Instead, there is a continuous flow of twitter data, which causes the program to freeze. Any fixes for this?
You need to use multithreading. Simplest to use (in my opinion) is the standard library multiprocessing module:
import multiprocessing
...
class MainView(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
p1 = Page1(self)
p2 = Page2(self)
buttonframe = tk.Frame(self)
container = tk.Frame(self)
buttonframe.pack(side="top", fill="x", expand=False)
container.pack(side="top", fill="both", expand=True)
p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p2.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
b1 = tk.Button(buttonframe, text="start", command=self.hello)
b2 = tk.Button(buttonframe, text="stop", command=quitprog)
b1.pack(side="left")
b2.pack(side="left")
p1.show()
def hello(self, *args):
p = multiprocessing.Process(target=hello)
p.start()
quitprog does not need to be run in a separate process,and can't be either, because then sys.exit won't terminate your program.
I only included the MainView class code - make sure to add the rest !

How can I prevent Tkinter slave widgets from dictating their own positions?

So I have two frames, a centered text frame and a toolbar with buttons in it. I want the toolbar to be on top side, so I tried self.toolbar.pack(side='top', pady=60) but it doesn't seem to be enough.
What happens is that the buttons, who are slaves of the toolbar frame, seem to be dictating their own position : if I pack one left, it goes on the left side of the entire application. Instead, I would like to be able to place once my toolbar frame, and then pack my buttons, side by side, so using something like the side attribute that currently change their global position.
How can I achieve this? Is my OOP approach bad written?
The whole block:
import tkinter as tk
class ToolbarButton(tk.Button):
def __init__(self, master, text, pixelref, *args, **kw):
super(ToolbarButton, self).__init__()
self.master = master
super(ToolbarButton, self).configure(text=text, image=pixelref, height=20, width=20, compound='center')
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
# Textframe
self.text_frame = tk.Frame(root, width=600, height=790)
self.text_frame.pack_propagate(False)
self.text_widg = tk.Text(self.text_frame, width=1, height=1)
self.text_widg.pack(expand=True, fill='both')
# Toolbar
self.toolbar = tk.Frame(root)
self.pixel = tk.PhotoImage(width=1, height=1)
self.bold_button = ToolbarButton(self.toolbar, 'B', self.pixel)
self.bold_button.pack(side='left', padx=4)
self.italic_button = ToolbarButton(self.toolbar, 'I', self.pixel)
self.italic_button.pack(side='left', padx=4)
self.underline_button = ToolbarButton(self.toolbar, 'U', self.pixel)
self.underline_button.pack(side='left', padx=4)
# Packing
self.toolbar.pack(side='top', pady=60)
self.text_frame.pack(expand=True)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Someone explained me the issue: the ToolbarButton class wasn't instantiating properly.
A way to correct this - and improve the syntax too:
class ToolbarButton(tk.Button):
def __init__(self, master, text, pixelref, *args, **kw):
super().__init__(master)
self.configure(text=text, image=pixelref, height=20, width=20, compound='center')

Tkinter window does not update correctly when running

I'm trying to make a Tkinter window show updated data but it only pops up after 13 seconds with just the last value. I want it to pop up and change the values on screen. Mind you, the big goal of this code is to take data from a database (which updates every 3 seconds) and show the data onscreen, while running continuously, so if the answer could include some pointers on the "after" or "update" functions it would be greatly appreciated!
Here is what I have so far.
from tkinter import *
import time
class GUI(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Tk.wm_title(self, "Main Window")
self.container = 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.frame = StartPage(self.container, self)
self.frames[StartPage] = self.frame
self.frame.grid(row=0, column=0, sticky=NSEW)
self.show_frame(StartPage)
def show_frame(self, controller):
frame = self.frames[controller]
frame.tkraise()
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.label = Label(self, text="Current ID:\n")
self.label.pack(padx=10, pady=10)
self.data_label = Label(self)
self.data_label.pack()
self.update_data()
def update_data(self):
var1 = StringVar()
for i in range(10):
var1.set(str(i))
self.data_label.config(text=str(i))
time.sleep(1)
main = GUI()
main.mainloop()
I can give you a partial answer. The reason you don't see any updates is that time.sleep() suspends the process and does not allow for tkinter to repaint the window.
You don't use the label textvariable correctly. Specify it in the label and the label will change as you change the textvariable.
You use both pack and grid at the same time which may cause problems.
I have not used after() in a class before so I don't know how to work it, but this example should give you some pointers. I'm keeping console printouts in the code so I can see what it does.
from tkinter import *
import time
class GUI(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Tk.wm_title(self, "Main Window")
self.container = Frame(self)
self.container.pack(side=TOP, fill=BOTH, expand=TRUE)
self.frames = {}
self.frame = StartPage(self.container, self)
self.frames[StartPage] = self.frame
self.frame.pack(side=TOP, fill=BOTH, expand=TRUE)
self.show_frame(StartPage)
def show_frame(self, controller):
frame = self.frames[controller]
frame.tkraise()
frame.update_data()
print('Done')
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.parent = parent
self.label = Label(self, text="Current ID:\n")
self.label.pack(padx=10, pady=10)
self.var1 = StringVar()
self.data_label = Label(self, textvariable=self.var1)
self.data_label.pack()
self.update_idletasks() # Calls all pending idle tasks
def update_data(self):
if not self.var1.get(): self.var1.set('0')
iteration = int(self.var1.get())
print('iteration', iteration)
if iteration < 3:
iteration = iteration + 1
self.var1.set(str(iteration))
self.update_idletasks() # Calls all pending idle tasks
time.sleep(1)
self.update_data()
main = GUI()
main.mainloop()
You will have to research after() yourself as I can't help you there.

Resources