So I came across a Python3 tkinter GUI code snippet and it doesn't have anything like root = Tk() but IT RUNS! I read this and it is really helpful. But my question is, if the tk window and interpreter is initiated when I create my first widget, how can I add more widgets to the root without specifying it? aka. What should I do when I want to add more widgets to the same program / same window, since I don't have a variable like root to store the root window object?
By the way, there was a controller class like this:
class Controller(tk.Tk):
def __init__ (self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
parentObj = tk.Frame(self)
self.allFrames = {}
...
Does it mean that the parentObj frame is the windows / outmost layer of frame in this app? How do I understand this class definition here? What is tk.Tk.__init__(self, *args, **kwargs) here for?
Controller is a subclass of tk.Tk. Controller is identical to tk.Tk but with enhancements. Thus, doing something=Controller(...) serves the same purpose as something=tk.Tk().
What should I do when I want to add more widgets to the same program / same window,
Use self as the parent if inside the class, use the instance of the class if outside.
class Controller(tk.Tk):
def __init__ (self, *args, **kwargs):
...
self.some_widget = tk.Label(self, ...)
... and ...
root = Controller()
some_other_widget = tk.Label(root, ...)
Does it mean that the parentObj frame is the windows / outmost layer of frame in this app?
No. The outmost "layer" is the instance of Controller. That is the root window. parentObj lives inside that window.
What is tk.Tk.__init__(self, *args, **kwargs) here for?
This is just the standard python way for a subclass to initialize its parent class.
Related
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()
I have a GUI where a few fields must be completed, one of them usually has values that are repeated. These values are stored in an excel.
I would like to create an initial list out of the values of a given column and then to keep updating that column from new inputs that are stored in the same excel - they would be captured by 'surnames_list_df'.
... here it loads the DF and several other things.
surnames_list_df = list(set(df['Surname'].values))
MAIN = GUI()
MAIN.surnames_list.append(surnames_list_df) **
...
MAIN.mainloop()
Then in the GUI code:
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
...
self.surnames_list = []
...
# SURNAMES.
tk.Label(self, text='Surname:').grid(column=0, row=11, sticky='e')
self.surname_in = AutocompleteEntry(self, completevalues=self.surnames_list)
self.surname_in.grid(column=1, row=11)
I believe the problem is where I put the **. I have tried several ways to update that list inside the Class but I haven't been sucessful. Might I need to send the list as an input in the class? I haven't touched that because I am not sure how I made it work and don't want to break it now that I have made it work.
Thanks in advance.
Ok, I finally figured it out and was modifying the Class to accept attributes.
... here it loads the DF and several other things.
surnames_list_df = list(set(df['Surname'].values))
MAIN = GUI(surnames_list_df)
...
MAIN.mainloop()
Then in the GUI code:
class GUI(tk.Tk):
def __init__(self, surname_list_df, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
...
self.surnames_list = surname_list_df
...
# SURNAMES.
tk.Label(self, text='Surname:').grid(column=0, row=11, sticky='e')
self.surname_in = AutocompleteEntry(self, completevalues=self.surnames_list)
self.surname_in.grid(column=1, row=11)
I want to use the Object-oriented concept to divide the UI into four sections(layout) with different sizes. So, each section contains different items(Plot, camera frame, Image, and label)
screen layout example
I have a GUI class:
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.title("Hello World")
self.resizable(width=False, height=False) # Don't resize UI
self.geometry('{}x{}'.format(500, 500)) # Size of UI
What is the solution to implement that?
I believe I should have a Layout class and the object of this should change its charachteristic.
class layout(tk.Frame):
def __init__(self,parent, *args, **kwargs):
tk.Frame.__init__(self,parent, *args, **kwargs)
I am programming a GUI using Tkinter. In one of the classes I have defined a variable (entry_filename) and would like to use it in another class. A part of the code is as follows:
class Loginpage(tk.Frame,Search):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.controller=controller
self.label_user=tk.Label(self, text="Username")
self.label_user.grid(row=0, column=0)
self.label_pass=tk.Label(self, text="Password")
self.label_pass.grid(row=1, column=0)
self.entry_user=tk.Entry(self)
self.entry_user.focus_set()
self.entry_user.grid(row=0, column=1)
self.entry_pass=tk.Entry(self,show="*")
self.entry_pass.grid(row=1, column=1)
self.button=ttk.Button(self, text="Login",command= self.Logincheck)
self.button.grid(columnspan=2)
def Logincheck(self):
global username
global password
try:
username=self.entry_user.get()
password=self.entry_pass.get()
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.ssh.connect(server, username=username, password=password)#input your username&password
button1 = ttk.Button(self, text="Click to Continue",command= lambda: self.controller.show_frame(Inputpage))
button1.grid(columnspan=2)
except:
tm.showerror("Login error", "Incorrect username/password")
class Inputpage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller=controller
self.filein_label=tk.Label(self,text="Input file name")
self.filein_label.grid(row=0,column=0)
self.entry_filename=tk.Entry(self)
self.entry_filename.focus_set()
self.entry_filename.grid(row=0,column=1)
self.button1 = ttk.Button(self, text="Click to Continue",command= lambda: self.controller.show_frame(Graphpage))
self.button1.grid(columnspan=2)
class Graphpage(tk.Frame,Inputpage):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller=controller
self.label = tk.Label(self, text="Graph Page!", font=LARGE_FONT)
self.label.pack(pady=10,padx=10)
button1 = ttk.Button(self, text="Back to Input Page",command=lambda: self.controller.show_frame(Inputpage))
button1.pack()
filename=Inputpage.entry_filename.get()
The Graphpage calls the variable filename which is later used to create the graph (that part of the code is omitted here). When the code is run the following error is returned:
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Frame, Inputpage
It seems that I have hit another roadblock in attempting to solve the earlier issue, however, if I can understand the resolution to this, I hope that I can attempt to solve further issues. Thanks for your help
ssh is a local variable inside function LoginCheck so you are not able to retrieve it from another class. One thing possible to do is to define ssh as self.ssh so it will be accessible through instance_of_Loginpage.ssh. It will work only when you will pass an instance of Loginpage into an instance of Graphpage. If you need access to an ssh connection from many places I suggest to create another class just to handle ssh (you can use Borg patter to achieve it).
The culprit is that you should not share
class member variables that way.
If different classes share some common
data, that data is probably another class
and they can inherit from it.
class CommonData():
client = 100
class A(CommonData):
def __init__(self):
print(A.client)
class B(CommonData):
def __init__(self):
print(B.client)
a = A()
b = B()
CommonData.client = 300
print(a.client)
print(b.client)
In above case every instance of A and every instance of B
share all the CommonData class variables, like client.
CommonData.client = 400
class C():
pass
You can use multiple inheritance too.
define all common data as CommonData attributes
and use CommonData as a class to hold data, like
in above example, don't create instances from it:
class D(C, CommonData):
def __init__(self):
print(D.client)
c = C()
d = D()
A simpler option would be to just define
a variable CommonData in the outer scope and
use it from anywhere:
common_data = 500
class A():
def __init__(self):
global common_data
print(common_data)
common_data = 200
# ...
But global variables are generally seen as a bad thing in a program as their use can become a problem for several reasons.
Yet another way is to pass the variable to the object initializer.
That makes the instance to keep its own value copied from
the creation value:
common_data = 600
class A():
def __init__(self, data):
self.common = data
print(self.common)
a = A(common_data)
common_data = 0
print(a.common)
If you run all the code above it will print
100
100
300
300
400
600
600
Edit:
See my comment to your answer and a simple example here.
Here I opt for two global references to tkinter StringVars.
The stringvars exist themselves in the Tk() namespace, like the
widgets; besides they are global Python names.
import tkinter as tk
from tkinter import ttk
class Page1(tk.Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.title('Page1')
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=input_file1)
self.label1.pack(side=tk.LEFT)
self.entry1.pack()
class Page2(tk.Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.title('Page2')
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=input_file2)
self.button1 = ttk.Button(self, text='Copy Here', command=copy_filename)
self.label1.pack(side=tk.LEFT)
self.entry1.pack(side=tk.LEFT)
self.button1.pack()
def copy_filename():
input_file2.set(input_file1.get())
root = tk.Tk() # has to exist for the StringVars to be created
root.iconify()
input_file1 = tk.StringVar()
input_file2 = tk.StringVar()
page1 = Page1(root)
page2 = Page2(root)
root.mainloop()
Now in the next example see how I turn the stringvars into variables
of Page1 and Page2 instances (not classes), making them local instead
of global. Then I am forced to pass a reference for the widget page1
object into the widget page2 object.
This looks more close to what you are asking.
About MRO trouble, if you avoid multiple inheritance
it won't happen.
Or you deal with it usually by using super()
In your case the error is because you store the widget in
the object/instance (in self.somename), and then you try
to invoke a widget method qualifying with the class name.
There is no widget there in the class for you to use a method.
So the search using the method resolution order fails,
because there is no corresponding name there.
Note that I have not used multiple inheritance, so I could
have just written tk.Frame. instead of calling super. I like
super because it makes clear in the text that I am invoking the parent
class but super is really needed only when there are multiple parents
and various levels of subclassing (usually forming a diamond shape).
Now the example:
import tkinter as tk
from tkinter import ttk
class Page1(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.input_file1 = tk.StringVar()
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=self.input_file1)
self.label1.pack(side=tk.LEFT)
self.entry1.pack()
class Page2(tk.Frame):
# note the page1 reference being
# passed to initializer and stored in a var
# local to this instance:
def __init__(self, parent, page1, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.page1 = page1
self.input_file2 = tk.StringVar()
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=self.input_file2)
self.button1 = ttk.Button(self, text='Copy Here',
command=self.copy_filename)
self.label1.pack(side=tk.LEFT)
self.entry1.pack(side=tk.LEFT)
self.button1.pack()
def copy_filename(self):
# see how the page1 refernce is used to acess
# the Page1 instance
self.input_file2.set(page1.input_file1.get())
root = tk.Tk() # has to exist for the StringVars to be created
page1 = Page1(root)
page2 = Page2(root, page1) # pass a reference to page1 instance
page1.pack(side=tk.LEFT)
page2.pack(side=tk.LEFT)
root.mainloop()
I need the buttons within LeftFrame to change its appearance when clicked. In the class AccOne, I tried to do left_frame.acc1.config(releif='SUNKEN'), but I get NameError: name 'left_frame' not defined. I tried making left_frame global, but no luck.
Here's the script.
class MainApp(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack()
container.rowconfigure(4, weight=1)
container.columnconfigure(2, weight=1)
right_frame = RightFrame(container, self)
left_frame = LeftFrame(container, right_frame)
left_frame.pack(side=LEFT)
right_frame.pack()
class RightFrame(Frame):
def __init__(self, parent, controller, *args, **kwargs):
Frame.__init__(self, parent, *args, **kwargs)
self.frames = {}
for F in (Welcome, AccOne, AccTwo, AccThree, AccFour, AccFive):
frame = F(self, self)
self.frames[F] = frame
self.show_frame(Welcome)
def show_frame(self, cont):
frame = self.frames[cont]
frame.grid(row=0, column=0)
frame.tkraise()
class LeftFrame(Frame):
def __init__(self, parent, controller, *args, **kwargs):
Frame.__init__(self, parent)
acc1 = Button(self, text="Account 1", width=12, height=3, command=lambda: controller.show_frame(AccOne))
acc1.pack()
I figured it would make sense to configure the button under def show_frame(self,cont): but I have no idea where to start since that method isn't under LeftFrame.
When creating tkinter windows with classes, try and think about creating a 'widget tree', this being a path through which you can access all of your widgets. In this simple example, MainWindow and SubWindow can access all of eachother's widgets:
class MainWindow(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# widget
self.lbl = tk.Label(self, text='Title')
self.lbl.pack()
# create child window, as class attribute so it can access all
# of the child's widgets
self.child = SubWindow(self)
self.child.pack()
# access child's widgets
self.child.btn.config(bg='red')
class SubWindow(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# can use this attribute to move 'up the tree' and access
# all of mainwindow's widgets
self.parent = parent
# widget
self.btn = tk.Button(self, text='Press')
self.btn.pack()
# access parent's widgets
self.parent.lbl.config(text='Changed')
Things to change in your code
Firstly, every time you create a widget that you might want to access later, assign it to a class variable. For example (this is part of the cause of your problem):
self.left_frame
self.acc1
not
left_frame
acc1
Secondly, make proper use of your parent and controller arguments. You're doing these, but you never use them or assign them to an atribute, so they may as well not be there. Assign them to a self.parent or self.controller attribute, so if you need to access them in a method later, you can.
I don't know exactly what you're trying to do and I can't see your AccOne class, but you should be able to find a way to access that button by making these changes.
Good luck!