How can I bind a click event to an image on tkinter? - python

I am trying to make the GUI for a logic circuit simulator. My idea is that when the picture/icon for a gate is clicked, the object that picture/icon belongs to becomes selected and saved to be used for a later function that links two different gates together. However, the issue lies in that there seems to be no way to bind a mouse event to a picture in tkinter the same way you would with a button.
When running the program, an attribute error is thrown from the line
self.root.orIcon.tag_bind(orGateObject.getIcon(), '<Button-1>', lambda: self.makeSelected(orGateObject.getIcon()))
AttributeError: 'PhotoImage' object has no attribute 'tag_bind'
The same error is thrown when using .bind instead of .tag_bind as well.
from tkinter import *
class orGate:
def __init__(self):
self.filePath = 'Project programs/gate pictures/or.png'
self.icon = None
def getFilePath(self):
return self.filePath
def makeIconBelongTo(self, icon):
self.icon = icon
def getIcon(self):
return self.icon
class Window:
def __init__(self):
self.root = Tk() # root widget (window)
self.root.title("Logic Circuit Simulator")
# tool section
self.toolFrame = LabelFrame(self.root, text="Tools", width=100, height=500)
self.toolFrame.config(bg="lightgrey")
self.toolFrame.grid(row=0, column=0, padx=5, pady=5)
#gates section within tool section
self.gatesLabelFrame = LabelFrame(self.toolFrame, text="Gates", width=5, height=1, borderwidth=1, relief="ridge")
self.gatesLabelFrame.grid(row=0, column=0, padx=5, pady=10)
# workFrame
self.workFrame = Frame(self.root, width=1000, height=550)
self.workFrame.grid(row=0, column=1, padx=5, pady=5)
#actual visual frame (the circuit goes in here) within workFrame
self.visualFrame = LabelFrame(self.workFrame, text="Workspace", width=1000, height=500)
self.visualFrame.config(bg="lightgrey")
self.visualFrame.grid(row=1, column=0, padx=5, pady=5)
#options bar frame
self.optionsLabelFrame = LabelFrame(self.workFrame, text="Options", width= 1000, height=30)
self.optionsLabelFrame.config(bg="lightgrey")
self.optionsLabelFrame.grid(row=0, column=0, padx=5, pady=5)
#canvas (workspace) within visual frame
self.workspace = Canvas(self.visualFrame, bg="lightgrey", width=1000, height=500)
self.workspace.grid(row=0, column=0, padx=5, pady=5)
#buttons
orButton = Button(self.gatesLabelFrame, text="OR", command=lambda:self.createOrGate(), relief="groove")
orButton.grid(row=2, column=0, padx=5, pady=5)
self.gatesAndIcons = []
self.selectedObject = None
def runWindow(self):
return self.root.mainloop()
def makeSelected(self, icon):
for i in self.gatesAndIcons:
if i[1] == icon:
self.selectedObject = i[0]
print("selection made") # check if selection was made
def createOrGate(self):
#instantiate OR gate
orGateObject = orGate()
orIcon = PhotoImage(file=orGateObject.getFilePath())
self.root.orIcon = orIcon # to prevent the image garbage collected.
orGateObject.makeIconBelongTo(self.root.orIcon) # making the icon an attribute of the or gate object
self.workspace.create_image(500, 250, image=orGateObject.getIcon())
self.gatesAndIcons.append([orGateObject, orGateObject.getIcon()]) # create object and icon pair so when icon is clicked on the canvas, the object it belongs to becomes the selected item
self.root.orIcon.tag_bind(orGateObject.getIcon(), '<Button-1>', lambda: self.makeSelected(orGateObject.getIcon())) # the issue here is the binding of the click event to the icon
print(self.selectedObject)
Window = Window()
Window.runWindow()

Related

Displaying images in a specific frame in Python using TKinter

I am trying to display images in my game, which in time will correspond to rooms.
I have three main frames and I want to display images in the one called 'frame 3', but I am not unable to get the picture itself to display!
I made a method called RoomImages which is supposed to handle the roomImages. I have called this method in my constructormethod for the whole App class.
It displays no error messages and therefore I am unsure where the missing logic is.
I have attached a picture of what the output shows:
Output
import tkinter as tk
from Game import Game
from tkinter import messagebox
from PIL import ImageTk, Image
class App():
# Creates a Frame for the application
# and populates the GUI ...
def __init__(self, root):
#super().__init__(master=win)
self.game = Game()
#A menubar with the options Quit' which destroys the window
# and 'about' which displays a message box with the info from the method showAbout
menubar = tk.Menu()
menubar.add_command(label="Quit", command=root.destroy)
menubar.add_command(label="About", command=self.showAbout)
root.config(menu=menubar)
# Create two frames owned by the window root
# In order to use multiple layout managers, the frames
# cannot share a parent frame. Here both frames are owned
# by a top level instance root.
self.frame1 = tk.Frame(root, width=600, height=250, bg='WHITE', borderwidth=2)
self.frame1.pack_propagate(0) # Prevents resizing
self.frame2 = tk.Frame(root, width=600, height=150, bg='LIGHT GREY', borderwidth=2, padx=175)
self.frame2.grid_propagate(0) # Prevents resizing
self.frame3 = tk.Frame(root, width=600, height=250, bg='LIGHT BLUE', borderwidth=2, padx=275)
self.frame3.grid_propagate(0) # Prevents resizing
# This packs both frames into the root window ...
self.frame1.pack()
self.frame2.pack()
self.frame3.pack()
#image canvas for frame3 to display the current room
self.canvas_for_image = tk.Canvas(self.frame3, bg='LIGHT GREY', height=250, width=600, borderwidth=0, highlightthickness=0)
self.canvas_for_image.grid(row=0, column=0, sticky='nesw', padx=0, pady=0)
#self.canvas_for_image.pack(expand=YES, fill=BOTH)
self.frame3.columnconfigure(0, pad=5)
self.frame3.columnconfigure(1, pad=5)
self.frame3.rowconfigure(0, pad=5)
self.frame3.rowconfigure(1, pad=5)
#make a gridstructure for frame2 to be able to precisely place my buttons.
self.frame2.columnconfigure(0, pad=5)
self.frame2.columnconfigure(1, pad=5)
self.frame2.columnconfigure(2, pad=5)
self.frame2.columnconfigure(3, pad=5)
self.frame2.columnconfigure(4, pad=5)
self.frame2.columnconfigure(5, pad=5)
self.frame2.rowconfigure(0, pad=5)
self.frame2.rowconfigure(1, pad=5)
self.frame2.rowconfigure(2, pad=5)
self.frame2.rowconfigure(3, pad=5)
# Now add some useful widgets ...
self.textArea1 = tk.Label(self.frame1, text='')
self.textArea1.pack()
self.cmdArea = tk.Entry(self.frame2, text='')
self.cmdArea.grid(row = 3, column = 1)
self.buildGUI()
self.roomImages()
def showAbout(self):
"""uses the messagebox import to display info about the game"""
messagebox.showinfo("About", "A classic whodunnit where you have to find out who the murder is!")
def buildGUI(self):
self.doCmd = tk.Button(self.frame2, text='Run command',
fg='black', bg='blue',
command=self.doCommand)
self.doCmd.grid(row = 1, column = 1)
self.goDown = tk.Button(self.frame2, text='Down',
fg='black', bg='red',
command=self.processDownCommand)
self.goDown.grid(row=2, column=1)
self.goUp = tk.Button(self.frame2, text='Up',
fg='black', bg='red',
command=self.processUpCommand)
self.goUp.grid(row =0, column=1)
self.goLeft = tk.Button(self.frame2, text='Left',
fg='black', bg='red',
command=self.processLeftCommand)
self.goLeft.grid(row=1,column = 0)
self.goRight = tk.Button(self.frame2, text='Right',
fg='black', bg='red',
command=self.processRightCommand)
self.goRight.grid(row=1, column =2)
self.help = tk.Button(self.frame2, text='Help',
fg='black', bg='green',
command=self.processHelpCommand)
self.help.grid(row=1, column=4)
self.textArea1.configure(text=self.game.printWelcome())
def roomImages(self):
self.imageDict = {'apple': 'apple.png', 'banana': 'banana.png', 'bar': 'bar.png', 'cherries': 'cherries.png',
'grapes': 'grapes.png', 'lemon': 'lemon.png', 'melon': 'melon.png', 'orange': 'orange.png'}
self.apple = 'apple'
self.apple = f'images/{self.imageDict[self.apple]}'
#adding images
self.r1 = ImageTk.PhotoImage(Image.open(self.apple))
self.r1Label = tk.Label(self.frame3, image=self.r1)
def main():
win = tk.Tk() # Create a window
win.title("Adventure World with GUI") # Set window title
win.geometry("700x400") # Set window size
win.resizable(False, False) # Both x and y dimensions ...
# Create the GUI as a Frame
# and attach it to the window ...
myApp = App(win)
# Call the GUI mainloop ...
win.mainloop()
if __name__ == "__main__":
main()

Trying to use a canvas oval item as a status indicator

I am trying to color the canvas oval item, indicating when the program is busy (status indicator). The expected behavior is to turn the canvas oval red when clicking the check button, and turn it back off (to blue ) when the function is complete.
Here is the code I have so far
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.entryframe = Frame(self, width=800, height=500)
self.entryframe.pack(fill=X, padx=8, pady=8)
self.canvas = Canvas(self.entryframe, width=30, height=30)
# change fill color of the status oval
self.status = self.canvas.create_oval(10, 10, 30, 30, fill="blue", tags="state")
self.canvas.grid(row=0, column=1)
self.label = Label(self.entryframe, text="Enter your sentence:").grid(row=3, column=0, sticky=W)
self.text = Text(self.entryframe, wrap=WORD, width=70, height=10)
self.text.grid(row=4, column=0, sticky=W)
self.btn_h = Button(self.entryframe, text="Check", width="15", command=self.check_input).grid(row=5, column=0, padx=8, pady=8)
def check_input(self):
#change status oval color to red
self.canvas.itemconfig(status, fill='red')
# after the function is complete turn it back off
canvas.itemconfig(light_1, fill='blue')
root = App()
root.mainloop()
The current behavior is the item stays blue color, and does not change at all.
You saved the reference to the oval as a class attribute. So you will need to access it the same way by passing self:
import tkinter as tk
from threading import Thread
import time
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.entryframe = tk.Frame(self, width=800, height=500)
self.entryframe.pack(fill=tk.X, padx=8, pady=8)
self.canvas = tk.Canvas(self.entryframe, width=30, height=30)
# change fill color of the status oval
self.status = self.canvas.create_oval(10, 10, 30, 30, fill="blue", tags="state")
self.canvas.grid(row=0, column=1)
self.label = tk.Label(self.entryframe, text="Enter your sentence:").grid(row=3, column=0, sticky=tk.W)
self.text = tk.Text(self.entryframe, wrap=tk.WORD, width=70, height=10)
self.text.grid(row=4, column=0, sticky=tk.W)
self.btn_h = tk.Button(self.entryframe, text="Check", width="15", command=self.check_input).grid(row=5, column=0, padx=8, pady=8)
def check_input(self):
#change status oval color to red
self.canvas.itemconfig(self.status, fill='red') #use self.status instead
t = Thread(target=self.lengthy_process,args=("Arg1","Arg2"))
t.start()
def lengthy_process(self,param1,param2):
print (param1, param2)
time.sleep(10) #simulate the time taken for processing
print ("Finished!")
self.canvas.itemconfig(self.status, fill='blue')
root = App()
root.mainloop()
Also, judging from how you inherit the instance of tk and create the rest of the widgets, it looks like you have a mix of both import tkinter as tk and from tkinter import *. This is generally considered bad practice - it is more reasonable to simply use import tkinter as tk so you know which widgets belong to tk, and which belongs to ttk if required. Also read Why is “import *” bad?

tkinter button command function not defined

I am trying to get the input from text box and trying to write them to a file:
I get the error as: retrieve_input is not defined.
Please help me to rectify my code:
coding:
import tkinter as tki
class App(object):
def __init__(self,root):
self.root = root
# create a Frame for the Text and Scrollbar
txt_frm = tki.Frame(self.root, width=600, height=400)
txt_frm.pack(fill="both", expand=True)
# ensure a consistent GUI size
txt_frm.grid_propagate(False)
self.txt1 = tki.Text(txt_frm, borderwidth=3, relief="sunken", height=4,width=55)
self.txt1.config(font=("consolas", 12), undo=True, wrap='word')
self.txt1.grid(row=0, column=1, sticky="nsew", padx=2, pady=2)
scrollb1 = tki.Scrollbar(txt_frm, command=self.txt1.yview)
scrollb1.grid(row=0, column=2, sticky='nsew')
self.txt1['yscrollcommand'] = scrollb1.set
button = tki.Button(self,text=u"Click command=retrieve_input)
button.grid(column=1,row=0)
def retrieve_input():
input = self.txt1.get("0.0",'END-1c')
with open('hello.txt','w') as f:
f.wite(input)
root = tki.Tk()
app = App(root)
root.mainloop()
In addition to the obvious typos, the problem is this line:
button = tki.Button(self,text="Click", command = self.retrieve_input)
Notice that the first parameter you pass to tk.Button is self. The first argument must be a widget, but you're giving it self which is not a widget. Perhaps you meant to use txt_form instead?

Python tkinter inserting string in texttbox out of a function

Trying to add text from a function into a textfield, but can't figure out how.
Every time when the Start button is clicked it should add text to the textfield.
import Tkinter
class GuiCreate:
def __init__(self,parent):
#Textbox
window = Frame(width=620, height=50)
window.place(x=25,y=320)
vscroll = Scrollbar(window)
hscroll = Scrollbar(window, orient='horizontal')
# create instance variable with "self"
self.listbox = Text(window, height=10)
self.listbox.pack(side=LEFT, fill=X, padx=5, pady=5, expand=1)
vscroll.config(command=self.listbox.yview, relief=SUNKEN)
hscroll.config(command=self.listbox.xview, relief=SUNKEN)
self.listbox.config(yscrollcommand=vscroll.set, relief=SUNKEN)
self.listbox.config(xscrollcommand=hscroll.set)
f7 = Frame(width=30, height=20)
f7.place(x=20,y=260)
srcButton = Button(f7, text="START", command=self.startProcess)
srcButton.pack(side='left')
def startProcess(self):
textinsert = 'abcdefg'
self.listbox.insert('end', textinsert)
root = Tk()
root.title("Clipfinder")
root.geometry('650x550+200+100')
root.configure(background = 'gray')
gui=GuiCreate(root)
root.mainloop()
Getting the Error: AttributeError: GuiCreate instance has no attribute 'listbox'
How can I send the string out of a function into the textbox?
THX
def __init__(self, parent):
#Textbox
window = Frame(width=620, height=50)
window.place(x=25,y=320)
vscroll = Scrollbar(window)
hscroll = Scrollbar(window, orient='horizontal')
self.listbox = Text(window, height=10)
self.listbox.pack(side=LEFT, fill=X, padx=5, pady=5, expand=1)
vscroll.config(command=self.listbox.yview, relief=SUNKEN)
hscroll.config(command=self.listbox.xview, relief=SUNKEN)
self.listbox.config(yscrollcommand=vscroll.set, relief=SUNKEN)
self.listbox.config(xscrollcommand=hscroll.set)
f7 = Frame(width=30, height=20)
f7.place(x=20,y=260)
srcButton = Button(f7, text="START", command=self.startProcess)
srcButton.pack(side='left')
Forgot to add listbox as an attribute. Otherwise it is just local to the init method..
Try to create the listbox as an instance variable with self:
from Tkinter import *
class GuiCreate:
def __init__(self,parent):
#Textbox
window = Frame(width=620, height=50)
window.place(x=25,y=320)
vscroll = Scrollbar(window)
hscroll = Scrollbar(window, orient='horizontal')
# create instance variable with "self"
self.listbox = Text(window, height=10)
self.listbox.pack(side=LEFT, fill=X, padx=5, pady=5, expand=1)
vscroll.config(command=self.listbox.yview, relief=SUNKEN)
hscroll.config(command=self.listbox.xview, relief=SUNKEN)
self.listbox.config(yscrollcommand=vscroll.set, relief=SUNKEN)
self.listbox.config(xscrollcommand=hscroll.set)
f7 = Frame(width=30, height=20)
f7.place(x=20,y=260)
srcButton = Button(f7, text="START", command=self.startProcess)
srcButton.pack(side='left')
def startProcess(self):
textinsert = 'abcdefg'
self.listbox.insert('end', textinsert)
root = Tk()
root.title("Clipfinder")
root.geometry('650x550+200+100')
gui = GuiCreate(root)
root.configure(background = 'gray')
root.mainloop()
You can learn more about classes and object-orient programming in python here, a few paragraphs down it
touches upon using self.

Python GUI frame and button layout, how to add frame and buttons to correct location?

def create_layout(frame):
frame = Frame(frame, bg = 'red')
frame.pack(side = LEFT, fill=BOTH)
b = Button(frame, text='Button1', command=pressed, padx = 20)
b.pack(pady = 20, padx = 20)
c = Button(frame, text='Button2', command=pressed, padx=20)
c.pack(pady = 20, padx = 20)
I got this code so far, assume that from Tkinter import * has already been called and the frame has already had its size and colour set. It should look like the picture below. However i can't ever get button 3 and 4 to the frame on the right, whenever i add a button it goes in the red frame.
OK, the first set of buttons, button 1 & 2 are in the "frame", buttons 3 & 4 should be left out.
So with buttons 1 & 2, open the frame with the bg of red, pack it with side=tk.LEFT, fill with both & expand it.
With buttons 3 & 4, just side them LEFT and expand. That will work like a treat ;-)
You need to add another frame that sits to the right, and then pack button3 and button4 into that. Maybe change the previous frame you have there to frame1 and then have:
frame2 = Frame(frame, bg = "yellow")
frame2.pack(side = RIGHT, fill = BOTH)
Then, create the buttons and pack them in. Hope this helps!
You have 2 frames, and 4 buttons.
Let us create a function called create_widgets() which will only consist in calling 2 other functions create_frames() and create_buttons()
For the frames, we use the grid() layout manager:
def create_frames(self):
self.left_frame = tk.Frame(width=140, height=140, background='red')
self.left_frame.grid(row=0, column=0)
self.right_frame = tk.Frame(width=300, height=140, background='gold2')
self.right_frame.grid(row=0, column=1)
This will create this interface:
Let us design create_buttons() in a way it only consists in calling to 2 different functions, each having a specific task:
create_left_frame_buttons() to create buttons for the left frame
create_right_frame_buttons() to create buttons for the right frame
Here is their simple implementation:
def create_buttons(self):
self.create_left_frame_buttons()
self.create_right_frame_buttons()
def create_left_frame_buttons(self):
self.button1 = tk.Button(self.left_frame, text='Button1')
self.button1.grid(row=0, column=0, padx=30, pady=20)
self.button2 = tk.Button(self.left_frame, text='Button2')
self.button2.grid(row=1, column=0, padx=30, pady=20)
def create_right_frame_buttons(self):
self.button1 = tk.Button(self.right_frame, text='Button3')
self.button1.grid(row=0, column=0, padx=20, pady=50)
self.button2 = tk.Button(self.right_frame, text='Button4')
self.button2.grid(row=0, column=1, padx=70)
Note that I used the options padx and pady to create a suitable spacing between the buttons.
Up to this moment, this is the resulting interface:
You can see both the left and right frames are shrinking, and the result is ugly. To fix this issue, we can set rid_propagate(0) for each frame.
So based on these observations and following Tkinter best practices, here is the full code:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, self.master)
self.configure_gui()
self.create_widgets()
def configure_gui(self):
self.master.title('Simple layout')
self.master.geometry('440x140')
self.master.resizable(0, 0)
def create_widgets(self):
self.create_frames()
self.create_buttons()
def create_frames(self):
self.left_frame = tk.Frame(width=140, height=140, background='red')
self.left_frame.grid_propagate(0)
self.left_frame.grid(row=0, column=0)
self.right_frame = tk.Frame(width=300, height=140, background='gold2')
self.right_frame.grid_propagate(0)
self.right_frame.grid(row=0, column=1)
def create_buttons(self):
self.create_left_frame_buttons()
self.create_right_frame_buttons()
def create_left_frame_buttons(self):
self.button1 = tk.Button(self.left_frame, text='Button1')
self.button1.grid(row=0, column=0, padx=30, pady=20)
self.button2 = tk.Button(self.left_frame, text='Button2')
self.button2.grid(row=1, column=0, padx=30, pady=20)
def create_right_frame_buttons(self):
self.button1 = tk.Button(self.right_frame, text='Button3')
self.button1.grid(row=0, column=0, padx=20, pady=50)
self.button2 = tk.Button(self.right_frame, text='Button4')
self.button2.grid(row=0, column=1, padx=70)
if __name__ == '__main__':
root = tk.Tk()
main_app = MainApplication(root)
root.mainloop()
Demo:

Categories