Python Tkinter canvas class inheritance - python

Using Tkinter 8.6, Python 3.7.3:
A friendly user here instructed me on how to have an image act like a button by the way of creating an Imgbutton class that is a subclass of Tkinter Canvas.
I have some questions regarding this code, here is the simplified version of it:
#!/usr/local/bin/python3
import tkinter as tk
from PIL import Image, ImageTk
class Imgbutton(tk.Canvas):
def __init__(self, master=None, image=None, command=None, **kw):
super(Imgbutton, self).__init__(master=master, **kw)
self.set_img = self.create_image(0, 0, anchor='nw', image=image)
self.bind_class( self, '<Button-1>',
lambda _: self.config(relief='sunken'), add="+")
self.bind_class( self, '<ButtonRelease-1>',
lambda _: self.config(relief='groove'), add='+')
self.bind_class( self, '<Button-1>',
lambda _: command() if command else None, add="+")
Questions:
When I create an Imgbutton object, the separated line above gets executed but I do not understand why.
Does the self.set_img correspond to an object of Imgbuttonor tk.Canvas class?
Is there any point here where an actual canvas is created? I believed you need to create a canvas before you can add anything to it.
This part might be unneccessary to mention but here I am creating an Imgbuttonobject:
root = tk.Tk()
but_img = tk.PhotoImage(file='button.png')
but = Imgbutton(root, image=but_img, width=but_img.width(),
height=but_img.height(), borderwidth=2, highlightthickness=0)
but.pack()
root.mainloop()

When I create an Imgbutton object, the separated line above gets executed but I do not understand why.
It's executed because it's part of the code. I'm not sure why you think it wouldn't be called. If you don't want it to be called, move it outside of the __init__ method.
Does the self.set_img correspond to an object of Imgbuttonor tk.Canvas class?
self refers to the instance of the Imgbutton class. set_img will be the identifier returned by the canvas when it creates the object on the canvas.
Is there any point here where an actual canvas is created?
Yes. Imgbutton is the canvas. That is how inheritance works: Imgbutton is a Canvas, with some enhancements. It gets created when you do but = Imgbutton(...). Though, perhaps a bit more accurately the actual canvas is created when you call super, which tells tkinter to create the object.

Related

Expand Python Tkinter Object Types

There is this question to discover existing types:
Getting Python Tkinter Object Type
However I've just developed tooltips (balloons) I've assigned to some buttons and would like to be able to recall all of them as a unique type. Also down the road I'd like to hand-craft canvas objects which will operate like pseudo buttons with <enter>, <leave>, <active>, <press> and <release> events. How might I declare a new object type for them?
If I understand your question correctly you want to check if an instance created is a instance of the custom class, it can be directly done using isinstance:
class CustomTooltip():
pass
cwidget = CustomTooltip()
btn = tk.Button(root)
print(isinstance(cwidget, CustomTooltip)) # True
print(isinstance(b, CustomTooltip)) # False
print(type(cwidget) == CustomTooltip) # Not recommended
It is recommended to use isinstance rather than type, from the docs:
The isinstance() built-in function is recommended for testing the type of an object, because it takes subclasses into account.
I did not quite get your 2nd question, also it is better to keep a single question focused around one main question rather than asking more than one question.
Object Oriented Programming is probably the solution.
If I want to create a "new type" of tkinter button I can sub-class it.
class MySpecialButton(tkinter.Button):
pass
This doesn't do anything special at the moment but will give it a unique type (If it have understood your interpretation correctly)
The following example from another one of my answers, creates a special button with custom behaviour for hovering over the button
class HoverButton(tk.Button):
def __init__(self, master, **kw):
tk.Button.__init__(self,master=master,**kw)
self.defaultBackground = self["background"]
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, e):
self['background'] = self['activebackground']
def on_leave(self, e):
self['background'] = self.defaultBackground
With regard to canvas object, You can obviously create classes for these too which can contain methods for moving/resizing the object. As to how to create custom events for these, you can use tag_bind(item, event=None, callback=None, add=None) to bind a call back to a canvas object. A quick example below
import tkinter as tk
class CanvasShape:
def __init__(self, canvas, callback = None):
self.canvas = canvas
self.id = canvas.create_oval(10,10,50,50,fill='blue')
self.canvas.tag_bind(self.id,'<Button-1>',callback)
def clicked(e):
print("You clicked on a shape")
root = tk.Tk()
c = tk.Canvas(root,width=200,height=200)
c.grid()
shape = CanvasShape(c,callback=clicked)
root.mainloop()
This will create a circle that when you click on it will fire an event that is received by the clicked function.

tkinter: How to access a StringVar and objects from a different class?

I'm having a simple tkinter two frame application with a Label, Entry and Button widget and I want to access a StringVar() of FrameOne with a Entry and Button of FrameTwo.
If have seen a lots of examples of code, but do not get how this is been done in my example below. Many programmers are using a controller. If I would use a controller, I end up from an error to another.
For example:
FirstFrame = FrameOne(mainWindow)`
TypeError: __init__() missing 1 required positional argument: 'controller'
Which I completely understand, because I do not pass anything into the new 'controller' class argument when calling the Frame class. But I do not know what I should pass into this to solve it. Perhaps it is also caused by the lack of knowledge of using class variables (any literature tips are welcome).
The same counts for the solution to inherit FrameOne into FrameTwo. I bump into the same amount of errors applying to my code.
Another thing is that many programmers have examples of two frames that are not visible at the same time, while in my example I have two frames underneath each other at the same time.
An different related issue that I have is, what if the label widget of FrameOne was a Text widget? How do I access the widget from FrameTwo.
I could make it work with globals, but I do not want to use such writing and I will keep the access widget problem anyhow.
Please find my code below:
import tkinter as tk
class AppWindow():
def __init__(self, master):
self.master = master
master.title("Test Application")
master.geometry("1060x680")
master.grid_propagate(False)
class FrameOne(tk.Frame):
def __init__(self, parent):
super().__init__()
self["borderwidth"]=5
self["relief"]="ridge"
self.LabelText = tk.StringVar()
self.LabelText.set("It is not working yet")
self.testlabel = tk.Label(self, textvariable=self.LabelText)
self.testlabel.grid(row=1, column=1)
class FrameTwo(tk.Frame):
def __init__(self, parent):
super().__init__()
self["borderwidth"]=5
self["relief"]="ridge"
self.testentry = tk.Entry(self)
self.testentry.insert("end", "This should be working")
self.testentry.grid(row=1,column=1)
self.testbutton = tk.Button(self, text="Test the label", command=self.updatelabel)
self.testbutton.grid(row=1,column=2)
def updatelabel(self):
FrameOne.LabelText.set(self.testentry.get()) #HOW TO FIX THIS CODE THE RIGHT WAY?
#Create a window as defined in the AppWindow class
mainWindow = AppWindow(tk.Tk())
#Create a Frame as defined in class FrameOne
FirstFrame = FrameOne(mainWindow)
FirstFrame.grid(row=0, column=0) #Positioning Frame on Window
#Create a Frame as defined in class FrameOne
SecondFrame = FrameTwo(mainWindow)
SecondFrame.grid(row=1, column=0) #Positioning Frame on Window
Like with any python object, you access an attribute of an object using a reference to the object.
In your case, updatelabel should look like this:
def updatelabel(self):
FirstFrame.LabelText.set(self.testentry.get())
Note: your use of uppercase characters for instance variables makes your code much harder to comprehend. I recommend following the naming guidelines in PEP8.

Python Tkinter. Fix attribute error "Event object has no attribute tk" when using binding to create a new window

I have created a menubar and I am trying to create keyboard shortcuts to open these links. The shortcut will run a function that runs a class. This class creates a new window. But I keep getting this error:
AttributeError: 'Event' object has no attribute 'tk'
I have gone through many answers to problems similar to mine but had no success. I read on multiple answers that an instance of the class needs to be created to which I have done.
What I can't understood is when clicking the link on the menubar it works.
In the TrackComicScreen class before the bind commands.
Here is a minimised version of my code.
from tkinter import *
class TrackComicScreen:
def __init__(self, master):
self.master = master
root.bind("<Control-Shift-A>", self.AddNewEntry)
def AddNewEntry(self, master):
addComic(master)
class addComic:
def __init__(self, master):
self.AddNewEntryWindow = Toplevel(master)
self.AddNewEntryWindow.geometry('460x440')
#WINDOW PROPERTIES
root = Tk()
root.geometry("1074x714")
currWindow = TrackComicScreen(root)
root.mainloop()
ERROR Message extract
addComic(master)
self.AddNewEntryWindow = Toplevel(master)
BaseWidget.__init__(self, master, 'toplevel', cnf, {}, extra)
BaseWidget._setup(self, master, cnf)
self.tk = master.tk
AttributeError: 'Event' object has no attribute 'tk'
The expected result is for when Ctrl Shift A is pressed a new window should appear.
When you bind a function to a key, the function will be caused with one positional parameter which represents the event that caused the binding.
Consider this code:
root.bind("<Control-Shift-A>", self.AddNewEntry)
When self.AddNewEntry is called, tkinter will call it with one parameter, which is an object that represents the event (eg: self.AddNewEntry(event)). This object has properties such as the widget that received the event, the x/y coordinates of the mouse pointer, the key that was pressed, and so on.
You've named this parameter master which makes me think you're expecting that code to somehow represent the master or root window. And indeed, you pass that parameter to addComic, which then tries to use this event object as the master window for a Toplevel window. Since you can't use an event object as a master for another window, you get an error.
While you can modify the binding to pass master to the function, since you are storing master as an attribute of the class there is no need for you to do that -- AddNewEntry already knows what master is.
The solution is to rewrite AddNewEntry to accept the event parameter and ignore it, and pass self.master down to addComic:
def AddNewEntry(self, event):
addComic(self.master)
The bind generates an event when invoked, which is then passed to the callback function. So, in short, you are doing:
def AddNewEntry(self, event):
addComic(event)
And the event can't be the master of a Toplevel.

In Python, how to make "class" (when passed as a parameter) work the same as "class.attribute"?

So, I am currently making a custom Tkinter module/API mainly just so I can get a better understanding of Tkinter, but I've run into a problem. Suppose I have the following code:
import Tkinter as tk
Class Window(object): # Equivalent of Tkinters Tk() in my module
def __init__(self):
self.root = tk.Tk()
...
Class Label(object): # Equivalent of Tkinters label widget in my module
def __init__(self, master):
self.label = tk.Label(master, text="Hello world!")
...
How can I make it so when I'm creating a label widget, I may do
master = Window()
label = Label(master)
Instead of having to do
master = Window()
label = Label(master.root)
I know I can inherit tk.Tk into my window class, but I'm just curious if there's a different way of doing it. I've done some research and it seems it may have something to do with one of the built-in methods with the leading and trailing double underscores (not sure what they're called), but I don't know what most of those do.
P.S. Sorry for bad title, wasn't sure how to describe it in a short sentence :P
You can write:
self.label = tk.Label(master.root, text="Hello world!")
But as Brian and Paul said, inherit from tk.Tk is the best thing to do.

Tkinter button calling method on an object

I have some objects which are all instances of the same class; each object has (as one of its attributes) a tkinter button, and I want each tkinter button to perform a method on the object to which it belongs. I'm not really sure how to go about doing this. I've tried tinkering with lambda functions, and replacing "self" with "self_" in case tkinter was already passing "self" to the button's command, but none of this worked; I'm new to classes and hadn't come across lambda functions before today so it didn't surprise me. Example code is below - please could someone explain how to make it work in a way which is both simple, concise and pythonic, or if such a solution does not exist then provide a work around? Thanks in advance
import tkinter as tk
from tkinter import ttk
class SpecialButton():
def __init__(self, row):
self.button = ttk.Button(root, text="button", command=self.delete)
self.button.grid(row=row, column=1)
self.label = ttk.Label(root, text="label")
self.label.grid(row=row, column=2)
def delete(self):
self.button.forget()
self.label.forget()
#some other stuff
root = tk.Tk()
for row in range(3):
SpecialButton(row)
root.mainloop()
The only problem with your code is that you need to be calling grid_forget instead of forget.
Also, the code is a bit misleading -- the delete method doesn't actually delete anything, it just removes it from view. The widgets still exist and take up memory. Are you aware of that? If you truly want to delete the widgets, call the destroy method.

Categories

Resources