Problem Dynamically Drawing to Tkinter Canvas Object - python

One question: How do you dynamically draw to a Tkinter Canvas object from a button click?
This topic was posted originally by user339860 (see 1) as a two part question, but the second portion of the question has not yet been addressed. I'm experiencing the same problem, specifically I cannot draw to a Canvas object from a button event. Getting this question answered will help two people, myself and user339860; please take a look.
The application creates two frames positioned left and right, the second frame contains the Canvas object. I have a button in the first frame bound to a function/method named drawRectangle. The application runs fine, it even draws a rectangle in the Canvas object using the create_rectangle method, until you click the button. When you click the button you get a message with the following;
tkinter_app_27Nov2010.py", line 25, in drawRectangle
self.myCan.create_rectangle(64,64,110,110,fill='blue')
AttributeError: 'NoneType' object has no attribute 'create_rectangle'
I thought it might have something to do with the scope of the Canvas object, so I created a class level variable set to None, but that didn't fix the issue. I thought about the Canvas display list (see 2), but the Tk manual pages don't reflect a method of adding a new object that I could find.
CODE:
# tkinter_app_27Nov2010.py
from Tkinter import *
class Application(Frame):
myCan = None
def createWidgets(self):
uiFrame = Frame(self,width=300,height=30)
uiFrame.configure(background='#e0e0e0')
uiFrame.grid(row=0,column=0,sticky=N+S)
outputFrame = Frame(self,width=300,height=300,background='#C0C0C0')
outputFrame.grid(row=0,column=1)
newBtn = Button(uiFrame,text="testing",command=self.drawRectangle)
newBtn.grid(row=0,column=0)
fillLbl = Label(uiFrame,text='-').grid(row=1,sticky=N+S)
newLBL = Label(outputFrame,text="another testing",background='#C0C0C0')
newLBL.grid(row=0)
myCan = Canvas(outputFrame,width=300,height=300,borderwidth=1,relief='sunken')
myCan.grid(row=1)
myCan.create_rectangle(34,34,50,50,fill='red')
def drawRectangle(self):
self.myCan.create_rectangle(64,64,110,110,fill='blue')
def __init__(self,master):
Frame.__init__(self,master)
self.pack()
self.createWidgets()
root = Tk()
myApp = Application(master=root)
root.title("Tkinter Testing!")
myApp.mainloop()
There has to be a way to get a handle on the "damage/repair display model" (see 3) the Tkinter Canvas object uses to update itself. Please help!
References:
stackoverflow.com/questions/2824041/dynamically-add-items-to-tkinter-canvas
www.tcl.tk/man/tcl8.4/TkCmd/canvas.htm#M16
effbot.org/tkinterbook/canvas.htm#performance-issues

This is a Python problem, not a tkinter one. You have defined local variables inside createWidgets, but you haven't set them as instance attributes. You need to use self.foo for that:
>>> class Foo:
... def __init__(self):
... bar = "baz"
...
>>> class Bar:
... def __init__(self):
... self.bar = "baz"
...
>>> foo = Foo()
>>> foo.bar
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Foo instance has no attribute 'bar'
>>> bar = Bar()
>>> bar.bar
'baz'
Notice that you are indeed correct: the problem is to do with the scope of the Canvas. Or more precisely, with the scope of the myCan variable. If you hadn't defined the class variable myCan, the lookup self.myCan would have raised a revealing AttributeError.

There is no object "self.myCan". You have to create it as a canvas object or whatever before you can use it. You may also have to call update_idletasks() depending on what you do.

Related

How to access method of an object in the object?

I am trying to condense my code, so I want to create object instead of having to create labels each time I need one.
However, I can't figure out how to be able to change attributes of the object-labels using .config. I've tried using objectvariable.config(...), but that doesn't work. Neither does using a method like in the following:
class title_label():
def __init__(self):
self = tkinter.Label(root)
self.pack(side='left')
def update(self, text):
self.config(text=text)
Error-message is: objectvariable object has no attribute config.
How can I use .config on an object containing a label?
It should be
class title_label():
def __init__(self, root):
self.label = tkinter.Label(root) # <<< 'label' field here
self.label.pack(side='left')
def update(self, text):
self.label.config(text=text)
self hold the reference to the class itself. label is something that your class is supposed to hold not to be. Another approach would be to derive from the Label class, but for what it is worth storing the label in the field should be good enough for you.
If you made your class a subclass of tkinter.Label then it would have inherited a config() method from it.
Here's an example of how that might be done:
import tkinter as tk
class TitleLabel(tk.Label):
def update(self, text):
self.config(text=text)
if __name__ == '__main__':
root = tk.Tk()
title_lbl = TitleLabel(root, text='Initial Text')
title_lbl.pack(side='left')
root.after(1000, lambda: title_lbl.update('CHANGED!')) # Update after 1 sec.
root.mainloop()
But as you can see, there wouldn't really be much point of doing so, since the only thing update() does is forward to call on the base class.

Tkinter: AttributeError: 'NoneType' object has no attribute '_root'

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.

Trigger Tkinter events from a different class in Python

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)

Tkinter Frame object will not grid

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.

Can someone please explain how the second version of this code works?

I'm a beginner at Python and I'm currently on chapter 10/12 in my book 'Python Programming for the absolute beginner'. I understand OOP in Python but due to one chapter (which discusses some relevant information about OOP) being based around a program for playing 'Cards' I had to skip part of the chapter (because I don't know how to play Cards) and so I missed out on important information which I should know at this point in the book. The reason I'm saying this is because I will need a simple answer to my question because complex answers may be too difficult for me to interpret.
But anyway here is my problem, there is a piece of a code in my book which creates a simple GUI program (which I completely understand) and then there's an Object Oriented version of the program which I do not understand (main parts highlighted in '##'s). It contains something called a 'Superclass constructor' which completely confused me (I tried doing some research on it, but it just didn't make sense to me). If anyone can help explain to me how the second version of the code works (or give helpful resources) then I will be highly grateful:
First version of the code:
from tkinter import *
# create a root window
root = Tk()
root.title("Lazy Buttons")
root.geometry("200x85")
app = Frame(root)
app.grid()
bttn1 = Button(app, text = "This is a button")
bttn1.grid()
root.mainloop()
Second version:
from tkinter import *
class Application(Frame):
def __init__(self, master): # Why is 'master' called?
super(Application, self).__init__(master) # ?
self.grid()
self.create_widgets()
def create_widgets(self):
self.bttn1 = Button(self, text = "This is a button")
self.bttn1.grid()
root = Tk()
root.title("Lazy Buttons 2")
root.geometry("200x85")
app = Application(root)
root.mainloop()
Don't skip parts of a tutorial because you don't know the problem domain - you don't need to know how to play cards to understand how card game code relates to what the program does. To your actual problem:
class Application(**Frame**):
This creates a class Application that inherits from a class Frame. If you don't know inheritance, the tutorial you're following should explain it, or you could try this introduction to classes and inheritance that I just googled up.
**def __init__(self, master):
This creates a method, with the name __init__. This is a special method in Python, which behaves similarly to a constructor in other languages - essentially, whenever an Application is created, Python will immediately call its __init__ method before it gives the new object back to whoever made it. master is just an argument, same as any other to any other function.
super(Application, self).__init__(master)
This calls the constructor of the superclass, to let it initialise the new object. super(Application, self) figures out what the superclass is (in this case it is Frame; in more complex cases that you will come to eventually, this is harder to work out and super's magic becomes important).
self.create_widgets()**
This calls the method create_widgets, which you define below this. The object before the . gets passed into the method as the first argument, self - so, this calls a different method on the same object that the method you're in has been called on.
app = Application(root)
This creates an Application - objects are created by calling the class, like how list() creates a list. The argument you pass in gets handed to __init__ as its second argument (the new Application that Python creates behind the scenes gets passed as the first argument, self, as above). Python creates a new Application, calls its __init__ for you, and then assigns the new object to the name 'app'.
'super' part here is only to properly call constructor of base class (the class you are inheriting from.)
Let's consider following example:
class Device(): # class representing some drawing device
...
def fill(self, color)
... # actual code for draw on this device
screen = Device()
WHITE_COLOR = (255, 255, 255)
class Window(object):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class ColoredWindow(Window):
def __init__(width, height, color):
super(ColoredWindow, self).__init__(width, height)
self.color = color
def fill(self):
screen.fill(self.width, self.height, self.color)
my_window = ColoredWindow(640, 480, WHITE_COLOR)
my_window.fill()
The first class here, Window, is a generic window that has width and height attributes that are passed in constructor.
ColoredWindow is a subscass of Window, it has additional attribute color.
When ColoredWindow is constructed, it needs somehow pass width and height parameters to its baseclass Window.
This is exactly what construction super(ColoredWindow, self).__init__(width, height) is for. The first part, super(ColoredWindow, self), returns a reference to baseclass (Window in our case), and second part, __init__(width, height), just calls its constructor with right parameters.
In your example master is not called, it's just parameter to __init__ method of Application class.

Categories