I have an existing Python 3 program which I wrote quick-and-dirty without any object-oriented techniques (it was my first Python program). It's time I clean it up, and I'm having problems making tkinter work in my classes.
Here is a simplified example. It is only attempting to place a ttk.Entry widget into the class, which is inheriting from ttk.Frame:
import tkinter as tk
from tkinter import ttk
class MyClass(ttk.Frame):
def __init__(self, parent):
self.frame = ttk.Frame(parent)
# Entry widget
self.entValue = ttk.Entry(self.frame)
self.entValue.grid(column=0, row=0)
# tkinter init
root = tk.Tk()
# Make the class instance and place it
test = MyClass(root)
test.grid(column=0, row=0)
It gives me the following error:
Traceback (most recent call last):
File "{path_to_code}/so.py", line 17, in <module>
test.grid(column=0, row=0)
File "{path_to_python}\Python35-32\lib\tkinter\__init__.py", line 2072, in grid_configure
self.tk.call(
AttributeError: 'MyClass' object has no attribute 'tk'
What am I missing?
Your Frame hasn't been initialized. Add super().__init__(parent) to the top of your MyClass.__init__ method to allow ttk to initialize the frame.
Also, I think you can get rid of self.frame. Set self as the parent for the widgets in MyClass instead of self.frame.
Please Don't use the GRID method in Tkinter. It's the worst method in Tkinter.
Please use the Place() method to design your GUI. It's very easy to implement.
For more check details over Place() refer to this post how to i position buttons in tkinter?
The place method is not complex like grid(). Check the post to make your GUI better in Python.
Related
I have some general questions regarding working code below:
tkinter is library for graphic interface as I understand I can use it interchangeably with for example Kivy?
Would it be better to learn Kivy instead or other?
Lines import tkinter as tk and from tkinter import * do exactly the same, in the first one I have alias though?
In the code below, why do I have to use ttk in ttk.Progressbar?
I imported whole library with import tkinter as tk so why do i have to reimport ttk just for progress bar? (otherwise it is not working). I would expect to work sth. like tk.Progressbar
In the line btnProg = tk.Button(self.root, text = 'update', command=self.fnUpdateProgress), why method "fnUpdateProgress" can't have any variables? Whenever I add any, the button stop working? -> for example btnProg = tk.Button(self.root, text = 'update', command=self.fnUpdateProgress(24)) (ofc then some changes in def of the method itself)
I created progress bar (pb) as attribute of the class Test, but wolud it be better to define it as regular variable (without self)? To be honest, code works exactly the same.
Code:
import tkinter as tk
from tkinter import *
from tkinter import ttk
from CreateProgramMain import main
import GlobalVariables
class Test():
####################################################################################
def __init__(self):
self.Progress=0
self.root = tk.Tk()
self.root.title(GlobalVariables.strAppName)
self.root.geometry('400x200')
lbl = Label(self.root, text="Please choose environment.",font=("Arial Bold", 12))
lbl.grid(column=2, row=0,sticky='e')
def btnTestClicked():
main("TEST",self)
btnTest=tk.Button(self.root, text="Test Environment", command=btnTestClicked)
btnTest.grid(column=2, row=15)
#Place progress bar
pb = ttk.Progressbar(self.root,orient='horizontal',mode='determinate',length=200)
pb.grid(column=1, row=65, columnspan=2, padx=10, pady=20)
pb["value"]=self.Progress
pb["maximum"]=100
btnProg = tk.Button(self.root, text = 'update', command=self.fnUpdateProgress)
btnProg.grid(column=2, row=75)
self.root.mainloop()
def fnUpdateProgress(self): #why i cant insert variable inside?
pb["value"]=self.Progress
self.Progress=self.Progress+5
pb.update()
app = Test()
Thank you
it is upto you. However, tkinter and kivy both have their own syntaxes, commands, and their own usages. It might be a little difficult to convert tkinter code to kivy.
it is upto you
Yes. In the first, you have imported tkinter as tk. In the second one. You have done a wild card import. You have imported everything
Tkinter is a folder containing various modules. It contains a file called ttk.py which you have to import to access ttk.
All other classes like Label, Entry, Tk is present in __init__.py
you have to use lambda for it. If you call the function, it will be executed wlright away and the returned value is kept as the command.
Doing command=self.fnUpdateProgress(24)) will execute the function right away. Then, the returned value is kept as a command. Here, it returns None. So the command is nothing or the button is useless.
Use a lambda expression command=lambda: self.fnUpdateProgress(24))
if you don't add self it will be local to the function only. To access ot outside, it would have to be declared global, which is the point to avoid while using classes
I am so trash at coding that I have no idea how to utilise classes to make cool stuff :( --- I'm really new to GUI development, and I'm trying to make a simple maze game with a level selector. I have the maze program squared away, but I am somehow hopeless at Tkinter apparently, since I've been trying constantly for the last hour to find a solution online. As you might have noticed, this is my first post here.
I'm running this in PyCharm, using my decent computer on Windows 10. I'm especially trash at this IDE since I, for some reason, cannot install any libraries/ use any libraries that I see clearly installed in my list of libraries... but that's for another post. As I've mentioned, I've been trying to figure out a simple program for the past hour, but nothing seems to be working.
Nothing I find online is particularly useful, either, and the ones that might be are so hopelessly complex that I cannot understand what they are trying to achieve. I'm looking for a simple solution to a simple problem, and hopefully, this great community can help me out.
import tkinter as tk
class Window():
def __init__(self):
self = tk.Tk()
self.geometry("%dx%d+0+0" % (1920,1080))
root = Window()
root.mainloop()
Expected: Window appears
Observed: Program abruptly ends
Error:
Traceback (most recent call last):
File "C:/Users/(GD) ShadowPlague/PycharmProjects/GameDesign/Main.py", line 12, in <module>
root.mainloop()
AttributeError: 'Window' object has no attribute 'mainloop'
You create class in wrong way. You can't assign Tk() to self to create correctly class. External root will have nothing to do with internal self. First you create instance Window() and assign to variable root but later you create instance Tk() and assign to self but it will not change instance assigned to root.
First method: create Tk() inside class as self.root and then you use win.root
import tkinter as tk
class Window():
def __init__(self):
self.root = tk.Tk()
self.root.geometry("%dx%d+0+0" % (1920,1080))
win = Window()
win.root.mainloop()
Second method: inherit from Tk(). It needs Window(tk.Tk) and super().__init__
import tkinter as tk
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.geometry("%dx%d+0+0" % (1920,1080))
root = Window()
root.mainloop()
I am trying to figure out what the best way to communicate between different widgets is, where the widgets are custom classes inheriting from tkinter widgets and I have several frames present (to help with layout management). Consider for example the following simple gui (written for python 3, change tkinter to Tkinter for python 2):
from tkinter import Frame,Button,Tk
class GUI(Frame):
def __init__(self, root):
Frame.__init__(self,root)
self.upper_frame=Frame(root)
self.upper_frame.pack()
self.lower_frame=Frame(root)
self.lower_frame.pack()
self.upper_btn1 = Button(self.upper_frame, text="upper button 1")
self.upper_btn2 = Button(self.upper_frame, text="upper button 2")
self.upper_btn1.grid(row=0,column=0)
self.upper_btn2.grid(row=0,column=1)
self.lower_btn = CustomButton(self.lower_frame, "lower button 3")
self.lower_btn.pack()
class CustomButton(Button):
def __init__(self,master,text):
Button.__init__(self,master,text=text)
self.configure(command=self.onClick)
def onClick(self):
print("here I want to change the text of upper button 1")
root = Tk()
my_gui = GUI(root)
root.mainloop()
The reason I put them in different frames is because I want to use different layout managers in the two different frames to create a more complicated layout. However, I want the command in lower_btn to change properties of eg upper_btn1.
However, I can not immediately access upper_btn1 from the instance lower_btn of the customized class CustomButton. The parent of lower_btn is frame2, and the frame2 parent is root (not the GUI instance). If I change the parent of lower_btn to the GUI instance, ie
self.lower_btn = CustomButton(self, "lower button")
it will not be placed correctly when using pack(). If I change the parent of frame1/frame2 to the GUI instance, ie
self.upper_frame=Frame(self)
self.upper_frame.pack()
self.lower_frame=Frame(self)
self.lower_frame.pack()
I could in principle access the upper_btn1 from lower_btn by self.master.master.upper_btn1, BUT then the frame1/frame2 are not placed correctly using pack() (this I don't understand why). I can of course pass the GUI instance as separate variable, on top of master, to CustomButton, ie something like
class CustomButton(Button):
def __init__(self,master,window,text):
Button.__init__(self,master,text=text)
self.window=window
self.configure(command=self.onClick)
def onClick(self):
self.window.upper_btn1.configure(text="new text")
and then change the construction of lower_btn to
self.lower_btn = CustomButton(self.lower_frame,self, "lower button 3")
but is that the "correct" way of doing it?
So, what is the best way to reorganize this gui? Should I pass GUI as a separate variable on top of the master/parent argument? Should I change the master/parent of the buttons and/or the frames (and somehow fix the issues with the layout management)? Or should I make the commands of the buttons as methods of the GUI instance so they can communicate with the widgets in that GUI, although this does not really feel like object oriented programming? I can't seem to find a working object oriented design that feels "correct". Also, an explanation why pack() does not work for frame1/frame2 if I make the GUI instance (self) the master, would also be appreciated, as that would have been my intuitive, most object oriented, approach.
Edit: Maybe the best way is to not use frames inside frames at all, and use only grid() and then use colspan/rowspan to give the layout management more flexibility.
A year late, but: One way to communicate between widgets is to instantiate some kind of control center object that receives knowledge about the state of your widgets and then compels other widgets to act on that information. This 'manager' functionality will be independent of the layout of your widgets.
Here's an implementation that's customized to your example. The idea is to add a .manager attribute to the lower button, and to notify this manager when clicked. The GUI class remains unchanged.
from tkinter import Frame,Button,Tk
class Manager(object):
def __init__(self, gui):
self.gui = gui
self.gui.lower_btn.manager = self
def onClick(self):
self.gui.upper_btn2.configure(text="changed text")
class GUI(Frame):
def __init__(self, root):
Frame.__init__(self,root)
self.upper_frame=Frame(root)
self.upper_frame.pack()
self.lower_frame=Frame(root)
self.lower_frame.pack()
self.upper_btn1 = Button(self.upper_frame, text="upper button 1")
self.upper_btn2 = Button(self.upper_frame, text="upper button 2")
self.upper_btn1.grid(row=0,column=0)
self.upper_btn2.grid(row=0,column=1)
self.lower_btn = CustomButton(self.lower_frame, "lower button 3")
self.lower_btn.pack()
class CustomButton(Button):
def __init__(self,master,text):
Button.__init__(self,master,text=text)
self.configure(command=self.onClick)
self.manager = None
def onClick(self):
if self.manager:
self.manager.onClick()
else:
print("here I want to change the text of upper button 1")
root = Tk()
my_gui = GUI(root)
Manager(my_gui)
root.mainloop()
I am playing around with tkinter by creating a mortgage calculator. I am trying to create widgets dynamically from a separate file which contains lists with the relevant attributes.
The main file is:
import tkinter as tk
import tkinter.ttk as ttk
import tkinterwidgetinfo as twi
root=tk.Tk()
root.title('Tkinter Practice')
class Application(ttk.Frame):
def __init__(self, master=None):
super().__init__()
self.pack()
self.createApplication()
def createApplication(self):
## create widgets dynamically from twi
for i in twi.progWidgets:
a = i[1]+i[2]+'**'+str(i[3])+')'
i[0] = eval(a)
i[0].pack()
app = Application(root)
app.mainloop()
The list containing the widget information is in a separate file and imported. The information is as follows:
progWidgets = [
['inputFrame', 'ttk.LabelFrame(', '', {'text': "User Input",
'labelanchor': "nw"}],
['principalLabel', 'ttk.Label(', 'inputFrame,', {'text' : "Principal(£)"}],
['principalEntry', 'ttk.Entry(', 'inputFrame,', {}],
['termLabel', 'ttk.Label(', 'inputFrame,', {'text' : "Mortgage Term (Years)"}],
['termEntry', 'ttk.Entry(', 'inputFrame,', {}]
]
When I run this code, the first widget (the labelframe), isn't created. However, when I create the labelframe outside the loop, as follows:
import tkinter as tk
import tkinter.ttk as ttk
import tkinterwidgetinfo as twi
root=tk.Tk()
root.title('Tkinter Practice')
class Application(ttk.Frame):
def __init__(self, master=None):
super().__init__()
self.pack()
self.createApplication()
def createApplication(self):
inputFrame = ttk.Labelframe(text = "User Input",
labelanchor = "nw")
inputFrame.pack()
## create widgets dynamically from twi
for i in twi.progWidgets:
a = i[1]+i[2]+'**'+str(i[3])+')'
i[0] = eval(a)
i[0].pack()
app = Application(root)
app.mainloop()
The program behaves perfectly. How can I include the labelframe in the loop?
You use 'inputFrame' as a parent name but it is not defined. When you run your second piece of code inputFrame is defined and everything works.
I agree with commenters that using eval is a terrible idea (security reasons, flexibility), but you still can do this:
for i in twi.progWidgets:
a = i[0]+'='+i[1]+i[2]+'**'+str(i[3])+')'
eval(a)
a = i[0]+'.pack()'
eval(a)
To anyone finding this question, as you can tell from the content I was very new to programming at the time. From what I can remember I was under the naïve impression that organising the code in a very abstract manner was somehow saving processing time or somehow easier to read. I didn't have a grasp of the underlying mechanisms which dictate the operation of computers. With this in mind akarilimano's answer is correct and works but I would support the comments beneath the original question which criticise the use of such a bizarre layout.
From PEP 20
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Readability counts.
If the implementation is hard to explain, it's a bad idea.
#snakes and ladder
from tkinter import * #pygame is the module that has collections of functions that is used to create a window import everything from tkinter
import time
class window(Frame): #Frame comes from tkinter is what you think as a window
def __init__(self, master = None):#The master widget defines the settings upon initialisation
Frame.__init__(self, master) #This is called the frame class that has been built in python
self.master = master
def __init__window(self): #creation of the init window
self.master.title("Reagan Kambayi") #It changes the title of the title of our widget
self.pack(fill=BOTH, expand=1)#The pack function will pack this in our frame
#placing the button
stop = Button(self, master, message= "Stop")
#intialising the button that will start the stopwatch
stop.place(x=0, y=0)
screen = Tk() #It must be written with capitalised T because there will be an error and it holds the components of the tkinter module
screen.geometry("700x500")
app = window(screen) #The app variable is assigned to the window creation which has the tkinter module
screen.mainloop()
Ok, here we go.
from tkinter import * #pygame is the module that has collections of functions that is used to create a window import everything from tkinter
Pygame has nothing to do with tkinter and you're not importing pygame here, you're importing tkinter.
class window(Frame): #Frame comes from tkinter is what you think as a window
No, it isn't. A Frame widget is just that, a frame inside a window. It doesn't draw a window in and of itself. The parameter Frame in your example isn't even a Frame widget at all, it's value is Tk() which is the function called to draw the first window in tkinter.
def __init__(self, master = None):#The master widget defines the settings upon initialisation
I'm actually at a loss for what you're attempting to do here. master should equal Frame which equals screen which equals Tk() but if I'm correct you're overriding that and telling it to equal None?
Frame.__init__(self, master) #This is called the frame class that has been built in python
I don't think you're doing what you think you're doing here. This answer explains it better than I could.
def __init__window(self): #creation of the init window
If I'm reading your program correctly then window.__init__window() is never called, so none of this function ever actually happens.
self.pack(fill=BOTH, expand=1)#The pack function will pack this in our frame
You're attempting to call .pack() on self which is calling .pack() on Frame. Typically we wouldn't assign a value to self in this way (although this is valid), read this to find out what self should be used for.
#placing the button
stop = Button(self, master, message= "Stop")
This isn't placing the Button widget, this is assigning the Button widget to a variable. Also, Button widgets are declared as Button(parent, *attributes) and there is no message attribute built in. Meaning what you meant to call was Button(self.master, text="Stop").
#intialising the button that will start the stopwatch
stop.place(x=0, y=0)
This is where you're placing the button, but the function that contains this is never called, so it never happens.
app = window(screen) #The app variable is assigned to the window creation which has the tkinter module
What you're actually doing here is calling the class window, all this does in your current program is call window.__init__(), which in itself does essentially nothing.
This is meant with no offence but I think you're lacking a very basic understanding of tkinter and possibly even Pythonic OOP.
I believe what you're trying to do in your program is the below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.root.title("Reagan Kambayi")
self.stop = Button(self.root, text="Stop")
self.stop.place(x=0, y=0)
root = Tk()
App(root)
root.mainloop()