Running into an issue trying to grid a framed object I created in a rudimentary paint program.
The instantiation code that gets the error is here:
from tkinter import *
from Menu import Menu
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self,master)
self.grid()
self.createWidgets()
def createWidgets(self):
#Imports each of the frames from the collection from the various widgets
menu=Menu()
menu.grid(row=0,column=0,columnspan=2)
app=Application()
app.master.title=('Sample Application')
app.mainloop()
The error I receive is related to the menu=Menu() operation and is:
TypeError: Menu() missing 1 required positional argument: 'Frame'
The Menu object is here:
from tkinter import *
import CommandStack
def Menu(Frame):
def __init__(self, master=None):
Frame.__init__(self,master)
self.createWidgets()
def createWidgets(self):
self.new=Button(self,command=CommandStack.new())
self.save=Button(self,command=CommandStack.save())
self.save.grid(row=0,column=1)
self.load=Button(self,command=CommandStack.load())
self.load.grid(row=0,column=2)
My confusion is how that positional error occurs. When I give the menu a frame (self), the grid method instead I get this error:
AttributeError: 'NoneType' object has no attribute 'grid'
I feel liking I'm missing a key part of working with frames, but I'm not sure what. Suggestions?
You seem to want Menu to be a class, and therefore defined it as such with the __init__ method. But you instead defined Menu to be a function, therefore all functions you stored inside are just defined as code that you would only use in the function. Change the def Menu to class Menu and it should be fine.
Related
I'm learning Tkinter and I'm trying to use an OOP approach in designing a simple GUI.
I'm creating a class that is inheariting from tkinter.Frame and also implements my specific functionalities.
I'm trying to define a tkinter.StringVar() outside the init function of my class and I get the error described in the title. Now I know that I should have my variable inside the init method and I got it to work I just want to understand why is not working if I declare it as a class variable.
I understand that tk.StringVar() needs to have a tk.Tk() context to be able to run. So this should(and indeed does) compile with no errors:
root = tk.Tk()
str = tk.StringVar()
root.mainloop()
If I write like this however it will give the mentioned error:
class myClass(tk.Frame):
def __init__(self, master):
super(myClass, self).__init__(master)
pass
str=tk.StringVar()
root = tk.Tk()
inst = myClass(root)
So this is if I would try to write tk.StringVar() without creating the 'root=tk.Tk()' first. But to my understanding the tk.Tk() context is created before I create the 'inst' instance so by the time the interpreter goes into the class and sees that class variable 'str' it should have already ran tk.Tk(). Obviously I'm missing something. Please explain to me the sequence in which things take place and why is that root.Tk() not executed before I try creating a tk.StringVar() variable. Thank you.
Edit: The line str=tk.StringVar() is part of the class body, and therefore executes during the definition of the class - which happens before Tk() is instantiated.
Thank you #jasonharper for the answer.
So I've been trying to get into using classes in my tkinter projects, but I seem to have trouble understanding exactly how classes interact. Especially when tkinter is involved. I can accesses variables and values and pass those around, but I can't seem to figure out how to do triggers.
My current problem is with trying to trigger an event from a different class.
A simple version of the problem is this:
from tkinter import *
class Area:
def __init__(self):
self.can = Canvas(width=100, height=100, bg="green")
self.can.pack(in_=window)
self.can.bind("<Button-3>", self.test)
self.square = self.can.create_rectangle(25, 25, 75, 75)
def test(self, event):
self.can.delete("all")
class Trigger:
def __init__(self):
self.button = Button(text="click me", command=?)
self.button.pack(in_=window)
window = Tk()
Area()
Trigger()
window.mainloop()
It creates a green canvas with a square in the middle. When you right click the canvas, the square is removed. I then try to trigger that same behavior from a different class, demonstrated here with a button.
Problem is, I can't for the life of me figure out what to have as a command on the button.
I've tried command=Area.test, but then I get "
TypeError: test() missing 2 required positional arguments: 'self' and
'event'"
I've tried command=Area.test(Area, "event") and command=Area.test(self, "event"), but they return:
AttributeError: type object 'Area' has no attribute 'can'
and
AttributeError: type object 'Area' has no attribute 'can'
I also tried Area().test("event), which gave no error but gave me 2 instances of the canvas, one with the square and one without. The button did nothing then.
Looked into inheritance, so I tried that by putting Area as inheritance on the Trigger class, then do command=self.test("event")
But then got:
AttributeError: 'Trigger' object has no attribute 'can'
So I'm out of ideas.. Am I doing the __init__part wrong?
First, if you want to use a function both as a target of an event and as the value for a command attribute, you should make the event argument optional. You can do that by giving it a default value of None:
def test(self, event=None):
self.can.delete("all")
Second, the Trigger object needs to be given the instance of the Area object so that it can call methods on that object. There are several ways to do this, but the most straight-forward is to pass it in when creating the object. This means you need to modify Trigger.__init__ to accept the parameter, and then you need to pass it in when creating the object.
This is how to modify the __init__ method:
class Trigger:
def __init__(self, area):
self.button = Button(text="click me", command=area.test)
self.button.pack(in_=window)
This is how to pass the Area object to the Trigger object:
area=Area()
Trigger(area)
To make available an instance of the ttk.Style() class, it was illustrated in this tkinter guide that the syntax is:
import ttk
s=ttk.Style()
When typing these command in IDLE, I noticed that ttk.Style() actually has a predefined argument, i.e.
s=ttk.Style(master=None)
I have written the following test script:
import tkinter as tk
import tkinter.ttk as ttk
class App(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self, parent, style='App.TFrame', relief=tk.SUNKEN,
border=10)
self.parent = parent
self.__createStyle()
self.__createWidgets()
def __createStyle(self):
self.s = ttk.Style()
self.s.configure('.', background='orange', border=100)
self.s.configure('App.TFrame', background='yellow')
self.s.configure('Btn.TButton', background='light blue', border=10)
def __createWidgets(self):
self._label = ttk.Label(self.parent, text='Label packed in root.')
self._label.pack()
self._btn = ttk.Button(self, style='Btn.TButton', command=self.__click,
text='Button packed inside self or class App, which is a ttk.Frame')
self._btn.pack()
def __click(self):
return print('Left Button Clicked!')
class myWidget(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self, parent, style='my.TFrame', relief=tk.GROOVE,
border=10)
self.parent = parent
self.__createStyle()
self.__createWidgets()
def __createStyle(self):
self.s = ttk.Style()
self.s.configure('my.TFrame', background='purple')
self.s.configure('my.TLabel', background='pink', border=10)
self.s.configure('my.TEntry', foreground='red', border=10)
def __createWidgets(self):
self._label = ttk.Label(self, style='my.TLabel',
text='myWidget Label packed in self or class myWidget, which is a ttk.Frame.')
self._label.pack()
self._entry = ttk.Entry(self, style='my.TEntry')
self._entry.pack()
if __name__ == "__main__":
root = tk.Tk()
root.title('Test Style')
root.geometry('500x150')
a = App(root)
a.pack(fill='both', expand=1)
b = myWidget(a)
b.pack()
root.mainloop()
Question 1: When do I need to declare the master arguement in ttk.Style()? E.g. in the above script, if I write self.s = ttk.Style() and self.s = ttk.Style(master=self.parent) in class myWidget, I get the same result (see Fig1).
Question 2: Is there a need to prefix s=ttk.Style() with self? I get the same result as shown in Fig1 with and without the self prefix.
Question 3: If I rename 'my.TFrame' in class myWidget as 'App.TFrame'(this name was used in class App), the background colour of the class App changed to purple color too (same color as class myWidget. Why did this happened given that variable name in different classes are unique?
Question 4: The names 'App.TFrame' and 'my.TFrame' were called before it was declared. Why did python or tkinter not complain or give an error but allowed the script to execute?
Figure 1
Figure 2
When do I need to declare the master arguement in ttk.Style()?
Probably never, except the case when tkinter doesnt support the default root. When you pass None as the master, the master becomes the current root instance of Tk class.
The main purpose of the master (root or any tk-widget) is to delegate instance of tk to the Style, so that the Style could be able to execute Tcl-related commands.
No more, no less.
Is there a need to prefix s=ttk.Style() with self?
It depends on your requirements. In context of your code - self is meaningless, because you're setting up styles in a scope of the __createStyle function.
Otherwise, if you wish to keep the reference, it makes sense to prefix with self.
If I rename my.TFrame in class myWidget as App.TFrame(this name was used in class App), the background colour of the class App changed to purple color too (same color as class myWidget. Why did this happened given that variable name in different classes are unique?
Because both of classes share the same frame style, hence the same color. Created style is a global thing, it can be chaged at runtime, and all the relevant widgets will react to these chages.
The names App.TFrame and my.TFrame were called before it was declared. Why did python or tkinter not complain or give an error but allowed the script to execute?
Why you think they should? When you pass something like <any_sensible_name>.<any_relevant_and_existing_basestyle>, ttk knows that you want a variation of a base style, so it's implicitly creates one, which inherits all base properties.
Try that trick with something more meaningless, like your current style name without dot (ttk.Frame.__init__(..., style='AppTFrame', ...)), which gives you the desired error:
_tkinter.TclError: Layout AppTFrame not found
Only a partial Answer, but I suppose #Bryan Oakley will entlighten us sooner or later.
Question 3:
If you use "App.TFrame" instead of "my.TFrame" inside your MyWidget Class, you override the predefined style properties.
Short example:
If you style "TFrame", all "TFrame"( == Tkinter.Frame/ttk.Frame ) instances will be affected.This is also sometimes referred to as "root-Style".
If you define another "somename.TFrame" and set it for one Object of type frame, it will be styles according "somename.TFrame".
Question 4:
The lookup names only override default styles. As long as they have no properties, they do not override a thing.
This "assignment" results in a tcl call and has no specific error handling inside the Tkinter / ttk Sources (used in BaseWidget class).
I can only tell that tcl does not throw an error here but I am not a tcl expert myself.
I hope this at least helps a bit.
I am trying to create a simple timer in Python and am aiming to build the user interface using classes. I would like to use the classes to initialise user interface. Then in the main text of the body, I would like to add attributes using the .grid and .configure methods. But when I try to do this, the error: 'statInter' object has no attribute 'tk' appears.
I am a beginner in programming, but if I understand the error correctly the it results because the .grid and other Button methods are not inherited by my statInter (i.e. static interface) class. Is this correct? How do I solve this error? I trued inheriting the properties of Button class and even Tk class, but in the later case I get an infinite loop i.e. maximum recursion depth exceeded.
Thanks for your help
#This is a simple timer version
from tkinter import *
window = Tk()
window.title('Tea timer')
window.minsize(300,100)
window.resizable(0,0)
class statInter(Button,Entry):
def __init__(self, posx, posy):
self.posx = posx # So the variables inside the class are defined broadly
self.posy = posy
def button(self):
Button(window).grid(row=self.posx, column=self.posy)
def field(self):
Entry(window, width=5)
sth = statInter(1,2)
sth.grid(row=1, column = 2)
window.mainloop()
The problem is your derived StatInter class (CamelCasing the class name as suggested in PEP 8 - Style Guide for Python Code) doesn't initialize its base classes, which generally doesn't happen implicitly in Python (as it does in say, C++).
In order to do that from within the StatInter.__init__() method, you're going to need to know the parent widget that will contain it (all widgets except the top level window are contained in a hierarchy) — so an extra argument needs to be passed to the derived class's constructor so it can be passed on to each of the base class constructors.
You haven't encountered another problem yet, but likely will soon. To avoid it, you're also going to have explicitly pass self when explicitly calling the base class methods in button() and field().
from tkinter import *
window = Tk()
window.title('Tea timer')
window.minsize(300,100)
window.resizable(0,0)
class StatInter(Button, Entry):
def __init__(self, parent, posx, posy): # Added parent argument
Button.__init__(self, parent) # Explicit call to base class
Entry.__init__(self, parent) # Explicit call to base class
self.posx = posx # So the variables inside the class are defined broadly
self.posy = posy
def button(self):
Button.grid(self, row=self.posx, column=self.posy) # Add self
def field(self):
Entry.config(self, width=5) # Add self
sth = StatInter(window, 1, 2) # Add parent argument to call
sth.grid(row=1, column=2)
window.mainloop()
The reason you get this error is because you never invoke either of the constructors from the classes you're inheriting from (either Button or Entry).
If you change your __init__ to be:
def __init__(self, posx, posy):
Button.__init__(self)
self.posx = posx # So the variables inside the class are defined broadly
self.posy = posy
Then you won't get the error you were having previously, and a little window pops up. In the new __init__, we explicitly invoke Button's constructor.
Unlike Java and some other languages, the super constructor is NOT invoked by default. I assume that each class inheriting from other tkinter classes must have a tk field. By invoking the parent constructor of your choice, this field will be created. If you don't invoke a parent constructor, though, then this will not be an established field, and you'll get the error you have described ('statInter' object has no attribute 'tk').
HTH!
I have a simple graphical user interface using tkinter which gives a blank canvas on which the cursor places a green square wherever it is clicked.
I am trying to code in a "Clear all" button at the top of the interface which clears the entire canvas (whilst retaining the cursor's ability to draw squares when it clicks on the canvas again):
import tkinter as tk
class Interface(tk.Frame):
def __init__(self, master):
super().__init__(master)
tk.Button(self, text="Clear all", command=self.clearall).pack(side = tk.LEFT)
#What the heck do I do here?
def clearall(self):
#self._canvas.delete(tk.ALL)
pass
class App(object):
def __init__(self, master):
master.title("Doodle test")
master.geometry("600x600")
self._interface = Interface(master)
self._interface.pack()
self._canvas = tk.Canvas(master, bg='white')
self._canvas.pack(fill=tk.BOTH, expand=1)
self._canvas.bind("<Button-1>", self.leftclick)
# And would I need to put something here?
def plz_run_me_when_clearall_method_is_called(self):
self._canvas.delete(tk.ALL)
def leftclick(self, arg):
d = 10
self._canvas.create_rectangle([(arg.x, arg.y), (arg.x+d, arg.y+d)], fill="green")
root = tk.Tk()
app = App(root)
root.mainloop()
Ordinarily, this would be a simple task if the entire program was constructed within a single class, e.g.:
def clearall(self):
self._canvas.delete(tk.ALL)
Since there are two classes, I cannot call the .delete(tk.ALL) method for _canvas since it is declared in the App class. So how could I convey a button to execute function X which affects item Y from another class?
EDIT: Still trying to think of an appropriate title for this post.
A simple rule of python (not just tkinter) is that, if an object needs a resource in another object, that first object needs a reference to the second object.
That means either passing in the instance of App to the instance of Interface, or passing in the canvas itself.
Using tight coupling
For example, you could pass in the canvas this way:
class Interface(tk.Frame):
def __init__(self, master, canvas):
...
self._canvas = canvas
...
def clearall(self):
self._canvas.delete(tk.ALL)
class App(object):
def __init__(self, master):
...
self._canvas = tk.Canvas(master, bg='white')
self._interface = Interface(master, self._canvas)
...
There are many ways to accomplish a similar thing. For instance, instead of passing in the canvas you can pass in the reference to the app itself (eg: self._interface = Interface(master, self). You could then either makeself._canvas` "public", or you could provide a getter that the returns the canvas.
The potential downside to the above approach is that it tightly couples the two classes together. That is, if App some day is remade to draw on an image object rather than a canvas object, Interface will also have to be modified since it depends on a specific implementation in App (namely, that it does all its work on a canvas).
Using loose coupling
Loose coupling means that Interface doesn't know or care how App is implemented, it only knows that App promises to provide an API that Interface can use. In this way, App can completely change its implementation without breaking Interface, as long as it continues to provide the same API.
To keep these classes loosely coupled, you need to move add a clearall method into App. With this, Interface doesn't need to know that App uses a canvas, it just needs to know that App provides a way to clear the drawing surface, no matter what that drawing surface is.
class Interface(tk.Frame):
def __init__(self, master, app):
...
self._app = app
...
def clearall(self):
self._app.clearall()
class App(object):
def __init__(self, master):
...
self._canvas = tk.Canvas(master, bg='white')
self._interface = Interface(master, self)
...
def clearall(self):
self._calvas.delete(tk.ALL)