Trying to use a canvas oval item as a status indicator - python

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?

Related

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

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()

ttk Style won't apply to frame in class

just started working with tkinter and i wanted to change the style of the frame but it won't. Can't figure it out so i'm asking you guys.
from tkinter import *
from tkinter import ttk
from tkinter import font
import currencyapi
class Currency():
def __init__(self, parent):
# App settings
parent.title("CoinyAPP")
icon = PhotoImage(file="icon.png")
parent.iconphoto(False, icon)
parent.eval("tk::PlaceWindow . center")
# Font settings
highlightFont = font.Font(
family='Helvetica', name='appHighlightFont', size=12, weight='bold')
# Widgets
self.frame1 = ttk.Frame(
parent, style="Frame1.TFrame").grid(row=0, column=0)
ttk.Label(self.frame1, text="Pick a base currency.", font=highlightFont).grid(
row=0, column=0, padx=48, pady=20)
ttk.Label(self.frame1, text="Pick currency to convert into.", font=highlightFont).grid(
row=0, column=2, padx=18, pady=20)
self.amount = StringVar()
currencyAmount = ttk.Entry(self.frame1, textvariable=self.amount)
currencyAmount.grid(row=1, column=1, padx=40)
self.baseCurrency = StringVar()
base = ttk.Combobox(self.frame1, textvariable=self.baseCurrency, justify='center',
values=list(currencyapi.currencyData['rates'].keys()))
base.grid(row=1, column=0)
base.current(46)
self.convertInto = StringVar()
convert = ttk.Combobox(self.frame1, textvariable=self.convertInto, justify='center',
values=list(currencyapi.currencyData['rates'].keys()))
convert.grid(row=1, column=2)
convert.current(0)
ttk.Button(self.frame1, command=self.currency_convertion,
text='Convert!').grid(row=2, column=1, padx=40, pady=15)
self.result = StringVar()
ttk.Label(self.frame1, font=highlightFont,
textvariable=self.result).grid(row=3, column=1, pady=5)
def currency_convertion(self, *args):
self.conv = currencyapi.currencyData['rates'].get(
self.convertInto.get())
self.amountNumber = self.amount.get()
self.base = currencyapi.currencyData['rates'].get(
self.baseCurrency.get())
Ans = (float(self.conv) * float(self.amountNumber)) / float(self.base)
self.result.set("%.2f" % Ans)
root = Tk()
s = ttk.Style()
s.configure("Frame1.TFrame", background='yellow',
foreground='blue')
Currency(root)
root.mainloop()
It's probably not very well written, sorry for that! Started programming few weeks ago.
Tried to put it into Currency class and outside, both didn't work.
It's possible that the problem is here:
self.frame1 = ttk.Frame(parent, style="Frame1.TFrame").grid(row=0, column=0)
The geometry manager methods (pack, grid, place) return None, so self.frame1 is evaluating to None
To fix this, declare your frame and then put it on the grid separately:
self.frame1 = ttk.Frame(parent, style="Frame1.TFrame")
self.frame1.grid(row=0, column=0)
That said, I put together a quick boilerplate app to test this and it seemed to work without issues...
"""Tkinter Boilerplate App Example"""
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
super.__init__()
self.geometry('200x200')
self.title('Stylish')
self.frame = ttk.Frame(self, style='Frame1.TFrame')
self.frame.pack(expand=True, fill=tk.BOTH) # fill available space
self.style = ttk.Style()
# note: 'foreground' doesn't appear to have any effect on a Frame
self.style.configure('Frame1.TFrame', background='pink', foreground='red')
if __name__ == '__main__':
app = App()
app.mainloop()

Why Tkinter isn't filling my window vertically

So I'm making my first GUI app using Tkinter and My issue is that whenever I ask for my window to fill my screen, there's more or less a quarter of it that stay empty of any widgets (like so), I have no idea where this is comming from.
Here's all the relevant code:
GUI.py (main GUI code)
from tkinter import *
from write_env import *
BGC = '#1e1e1e'
FGC = '#ced4d4'
root = Tk()
root.title("Gamebook Maker")
# Grid setup
root.grid()
root.grid_columnconfigure(0,weight=1)
root.grid_columnconfigure(1,weight=1)
root.grid_rowconfigure(0,weight=1)
root.grid_rowconfigure(1,weight=1)
root.update()
# Frames placement
write = write_environement(root, bgc = BGC, fgc = FGC)
write.grid(row=0, column=1, sticky='nsew')
paragraph_list = Listbox(root, width= 20, height= 35, bg = "#333333", fg = FGC)
paragraph_list.grid(row=0, column=0, sticky='nsew')
paragraph_list.insert(1, "test")
root.mainloop()
write_env.py (code for the widgets on the right side):
from tkinter import *
class write_environement(Frame):
""" class for the writing/plotting module v0.1"""
def __init__(self, parent, bgc, fgc):
Frame.__init__(self, parent, bg="#36393f")
self.grid(row=0, column=1, sticky='nsew')
self.grid_columnconfigure(0, weight= 1)
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1)
self.write_tab = Text(self, bg=bgc, fg=fgc, width= 80, height= 20, font=12)
self.write_tab.config(wrap=WORD)
self.write_tab.grid(row=0, column=0, sticky='nsew',padx=5, pady=5)
self.followup_list = Listbox(self, width= 120, height= 10, bg = "#333333", fg = fgc)
self.followup_list.grid(row=1, column=0, sticky='nsew',padx=5, pady=5)
self.followup_list.insert(1, "test")
edit: Atlas453's comments solved the issue, thanks

Adding a hovering option to my buttons in a class

I'm trying to add a hovering option for multiple buttons which I have already achieved, but I would like to do it in a class to save me adding the option to each button individually.
I'm coding in python and using tkinter for my GUI.
class GUIButtons():
def __init__(self, window):
self.window = window
self.Calculate = Button(window, command=GetUnits, text="Calculate", width = 19, background = "dark blue", fg="white")
self.Calculate.grid(row=1, column=4, sticky=NSEW)
self.ShowMethod = Button(window, command=ShowMethod, text="Show method", width = 19, background = "darkblue", fg="white")
self.ShowMethod.grid(row=1, column= 5, sticky=NSEW)
self.Submit = Button(window, command = lambda: GetCoordinate(Message), text="Submit", width = 6, height = 1, background = "dark blue", fg="white", font = 11)
self.Submit.grid(row=3, column = 3, sticky = NSEW)
self.Displacement = Button(window, text="Displacement", background = "Dark Blue", fg="white", font=11)
self.Displacement.grid(row=2, column=1, sticky= N)
Not sure how to bind the hover option just once for it to apply for all my buttons.
Any help would be highly appreciated!
See Instance and Class Bindings
But Tkinter also allows you to create bindings on the class and
application level; in fact, you can create bindings on four different
levels:
the widget class, using bind_class (this is used by Tkinter to provide
standard bindings)
and example
By the way, if you really want to change the behavior of all text
widgets in your application, here’s how to use the bind_class method:
top.bind_class("Text", "", lambda e: None)
So using bind_class with <Enter> and <Leave> you can do it.
--
EDIT: example - when mouse enter/hover any button then test() will be called.
from tkinter import *
# ---
def test(event):
print(event)
# ---
window = Tk()
# created befor binding
Button(window, text="Button #1").pack()
Button(window, text="Button #2").pack()
Button(window, text="Button #3").pack()
window.bind_class('Button', '<Enter>', test)
# created after binding
Button(window, text="Button #4").pack()
window.mainloop()
--
You can also create own Widget to change existing Widget.
Red button:
from tkinter import *
# ---
class RedButton(Button):
def __init__(self, parent, **options):
Button.__init__(self, parent, **options)
self['bg'] = 'red'
# or
#self.config(bg='red')
# ---
window = Tk()
RedButton(window, text="Button #1").pack()
RedButton(window, text="Button #2").pack()
RedButton(window, text="Button #3").pack()
window.mainloop()
or
class RedButton(Button):
def __init__(self, parent, **options):
Button.__init__(self, parent, bg='red', **options)
--
EDIT:
If you need to change only button colors on hover then you don't need to bind function. Button has activebackground= and activeforeground=.
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root, text="HOVER", activebackground='blue', activeforeground='red')
btn.pack()
root.mainloop()
see Button
EDIT: it can behave different on Windows, Linux and OS X

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